2 Punkte von GN⁺ 2023-08-26 | 1 Kommentare | Auf WhatsApp teilen
  • Ein Programmierer, der Haskell von etwa 2009 bis etwa 2019 als Hauptsprache nutzte, betrachtet Haskell heute weder für private Projekte noch bei der Jobsuche noch als erste Wahl
  • Die Faszination von Haskell lag weniger in Monaden oder DSLs selbst als in algebraischer Code-Argumentation und dem sicheren Refactoring sowie dem Ausdrücken von Invarianten, das ein starkes Typsystem ermöglicht
  • Die Distanz entstand durch eine Kultur, die fortgeschrittene Typ-Features bevorzugt, wenig geschmeidige Werkzeuge und anhaltende inkompatible Änderungen in GHC und den Standardbibliotheken
  • Trotzdem bleiben Higher-Order-Funktionen, do-Notation, Lenses, deklarative Bibliotheken und fortgeschrittene Typabstraktionen Stärken, die andere Sprachen nur schwer ersetzen können
  • Haskell ist weiterhin sehr wertvoll, um ein besserer Programmierer zu werden, aber wer es zur Standardsprache einer Organisation machen will, muss die Kosten für Werkzeugqualität und Sprachstabilität in Kauf nehmen

Die Zeit, in der Haskell die Hauptsprache war, und die heutige Position

  • Etwa zehn Jahre lang war Haskell die Standardsprache für neue Projekte
    • Nach dem ersten Kontakt mit Haskell um 2009 sah sich der Autor bis zum Wechsel in einen Job, in dem hauptsächlich Ruby und C++ genutzt wurden, um 2019 selbst als Haskell-Programmierer
  • Haskell wurde für viele Zwecke eingesetzt, darunter Kommandozeilen-Tools, Web-Services, Grafik-Anwendungen und kleine Skripte
  • Heute lehnt der Autor Haskell nicht vollständig ab, aber es ist auch in privaten Projekten nicht mehr die Standardsprache, und gezielt nach einem „Haskell-Job“ wird ebenfalls nicht mehr gesucht
  • Diese Einschätzung bedeutet nicht, dass Haskell schlecht ist, sondern beschreibt eher die persönlichen Gründe, sich von Haskell entfernt zu haben

Warum Haskell so anziehend war

  • Die Eigenschaft, Code algebraisch behandeln zu können

    • Der größte Reiz von Haskell lag darin, dass sich über Code symbolisch und algebraisch argumentieren lässt
    • Wenn es zum Beispiel eine Funktion double n = n + n gibt, ist die Umformung von double x zu x + x in Haskell immer korrekt
    • In vielen imperativen Sprachen kann double(i++) anders funktionieren als (i++) + (i++)
      • Ersteres erhöht i einmal
      • Letzteres erhöht i zweimal
    • Dank dieser Eigenschaft lassen sich kleine mechanische Transformationen zu großen Refactorings aufstapeln, die deutlich sicherer sind
    • Ein großes Refactoring in einer Ruby-Codebasis kann sehr beunruhigend sein, in einer Haskell-Codebasis dagegen oft deutlich einfacher
  • Ein strenges, aber nicht übermäßig lautes Typsystem

    • Haskells Typsystem ist streng, aber dank Typinferenz nicht mit übermäßig vielen Typannotationen wie in Java verbunden
    • Typeclasses reduzieren den Aufwand auch dann, wenn man sich mit Typen befassen muss
    • Guter Haskell-Code hat eine „spröde“ Eigenschaft
      • Eine falsche Abstraktion verbiegt sich nicht stillschweigend so, dass ein fehlerhaftes Programm doch noch durchgeht
      • Wenn etwas nicht stimmt, schlägt die Kompilierung fehl
    • Der Typ NonEmpty beschreibt eine Liste mit mindestens einem Element
      • Operationen wie map, die die Länge erhalten, liefern NonEmpty zurück
      • Operationen wie append, die die Länge vergrößern, liefern ebenfalls NonEmpty zurück
      • Operationen wie filter, deren Ergebnis leer sein kann, liefern eine normale Liste zurück
    • An eine Funktion, die eine Dateipfadliste vom Typ NonEmpty verlangt, lassen sich Kommandozeilenargumente nicht einfach direkt übergeben; zuerst muss geprüft werden, dass sie nicht leer sind
    • Fügt man später einen Filter nach Dateiendung hinzu, kann das Ergebnis wieder leer sein und muss erneut geprüft werden; der Compiler verhindert dabei, dass versehentlich ein leerer Listepfad weitergereicht wird
  • Invarianten über Typen bewahren

    • Wer lange mit Haskell arbeitet, gewöhnt sich daran, Invarianten eines Programms in Typen auszudrücken und vom Compiler überprüfen zu lassen
    • Das bedeutet nicht nur komplexe Berechnungen auf Typebene
    • Ob SQL-Escaping bereits erfolgt ist, lässt sich mit Wrapper-Typen wie RawString und SanitizedString unterscheiden
      • Selbst wenn eine Web-API RawString liefert, kann man die Datenbankschicht so gestalten, dass sie nur SanitizedString akzeptiert
    • Eine Oberflächendarstellung mit vielen Maybe-Feldern lässt sich in eine interne Darstellung überführen, in der das Vorhandensein der benötigten Informationen garantiert ist
    • Man kann Abstraktionen auch nur so generisch machen, wie es für unabhängige Tests sinnvoll ist
  • Eine Sprache, in der DSLs und Higher-Order-Funktionen natürlich sind

    • In Haskell lassen sich auch ohne Makros leicht kompakte domänenspezifische Sprachen bauen
    • config-ini ist ein Beispiel für eine Bibliothek zum Umgang mit INI-Konfigurationsdateien
      • Statt einer imperativen Parsing-Schnittstelle nutzt sie eine deklarative Schnittstelle
      • Bestimmte Teile eines Konfigurationstyps werden auf bestimmte Teile der Struktur einer INI-Datei abgebildet
      • Mit derselben Schnittstelle lassen sich Lesen, Schreiben und diff-minimale Updates ausführen
    • Ruby-DSLs können auf dynamische Metaprogrammierung und globales Monkeypatching setzen, während sich Haskell-DSLs dank flexibler Syntax und Features wie Monaden-Overloading leichter auf bestimmte Dateien oder Codebereiche begrenzen lassen
    • Haskell macht auch den Umgang mit Higher-Order-Funktionen natürlich
      • (+) lässt sich unmittelbar als Funktion verwenden, die zwei numerische Argumente entgegennimmt und addiert
      • pairwiseProduct = zipWith (*) drückt die elementweise Multiplikation zweier Listen extrem kurz aus
    • Die Analogie passt, dass Haskell den Umgang mit Funktionen so selbstverständlich macht, wie Perl den Umgang mit Strings

Drei Gründe für die Distanz zu Haskell

  • Eine Atmosphäre, die schwer verständliche Stile bevorzugt

    • Der erste Faktor ist eine stilistische Vorliebe für neue, ausgefallene Techniken
    • Die Haskell-Community experimentiert ständig mit neuen Abstraktionen und baut sie aus
      • Ansätze aus abstrakter Algebra oder Kategorientheorie
      • Ansätze, die mehr Berechnung auf die Typebene verschieben, um fehlerhafte Zustände einzuschränken
    • Servants Type-Level-Web-API oder ein Beispiel, das ein Menüsystem mit einem Comonad ausdrückt, sind Experimente, die Problembereiche auf neue Weise modellieren wollen
    • Im Alltagscode erzeugen solche experimentellen Ansätze jedoch oft mehr Probleme, als die anfänglichen Vorteile vermuten lassen
    • In einem Beispiel aus einem Compiler-Projekt trugen AST-Knoten den Ausdruckstyp als Typparameter
      • Wenn man einen Syntaxbaum mit Typfehlern konstruierte, ließ sich der Compiler selbst nicht mehr kompilieren
      • Anfangs wurden so einige Bugs gefunden
      • Als viele Optimierungspässe nicht in die Typdisziplin passten, machten Casts und das Beschwichtigen von GHC den Code komplizierter
      • Am Ende wurde der Typparameter wieder aus dem AST entfernt
    • Bei etwa drei Vierteln der Projekte, die einen Ansatz mit „fancy types“ ausprobierten, fiel das Urteil aus, dass der Nutzen den Aufwand nicht rechtfertigte
    • Die Strömung Simple Haskell beruht auf der Idee, dass die Sprache ihren größten Nutzen bringt, wenn man experimentelle Features meidet und nur ein Minimum an Extensions verwendet
    • Komplexere Typ-Features haben sich jedoch breit in Community und Bibliotheken ausgebreitet
    • Komplexe Typabstraktionen können Änderungen und Wartung erschweren, und es gab Erfahrungen, bei denen der Versuch, bereits vereinfachte Abstraktionen wieder durch moderne Typabstraktionen zu ersetzen, zu mehr Code, schlechterer Performance und geringerer Lesbarkeit führte
    • Gleichzeitig bleibt Trees that Grow als Beispiel für ein nützliches „fancy type-level feature“, etwa für nach Compiler-Phasen indizierte Datenstrukturen
  • Werkzeuge, die im Alltag nicht wirklich glänzten

    • Der zweite Faktor ist ein unhandliches Tooling-Ökosystem
    • Für Haskell gibt es mehrere Build-Systeme und Entwicklungswerkzeuge, die fast Standardstatus haben
    • Diese Werkzeuge erledigen im Großen und Ganzen die nötigen Aufgaben, erreichten aber nie ein Niveau, das man wirklich „lieben“ würde
    • Es gab Tools wie ormolu, die mit minimalem Aufwand genau das taten, was sie sollten, aber andere erforderten wegen anhaltender Bugs und Workarounds ständige Aufmerksamkeit
    • Das Haskell-Tooling hat sich über zehn Jahre verbessert, aber Stand 2019 gab es nichts, das an die Benutzbarkeit und Stabilität von Rust Cargo heranreichte
    • Problematisch war auch eine gewisse Not-Invented-Here-Tendenz, ständig etwas Neues und angeblich Besseres bauen zu wollen
      • Selbst Pakettextformate sollten etwa mit hpack neu aufgezogen werden
      • Frustrierend war, dass solche Vorhaben über Jahre bei 95 Prozent Fertigstellung stehenblieben
    • Das ist kein Urteil, dass Haskells Werkzeuge schlecht seien, aber „eigentlich ganz okay“ reicht bei gestiegenen Erwartungen nicht mehr aus
  • Fortlaufende inkompatible Veränderungen

    • Der dritte Faktor ist die anhaltende Veränderung der Haskell-Sprache und von GHC
    • GHC gilt faktisch als die einzige vernünftige öffentlich verfügbare Haskell-Implementierung
    • Haskell versucht, frühere Fehler und Inkonsistenzen schrittweise zu beheben, selbst in der Standardbibliothek und in grundlegenden Teilen der Sprache
    • Solche Änderungen können die Sprache zu saubereren und konsistenteren Grundlagen führen, erzeugen für Nutzer aber fortwährend kleine Reibungen
    • Die Foldable-Traversable-Prelude-Änderungen von 2015 sind ein Beispiel für diese Entwicklung
    • Selbst ältere Haskell-Projekte ohne externe Abhängigkeiten lassen sich auf aktuellen GHC-Versionen möglicherweise nicht mehr bauen
      • hinzugefügte Superclasses
      • entfernte Imports
      • zusätzliche Typsignaturen wegen mehrdeutig gewordener Polymorphie
    • Die nötigen Anpassungen sind meist klein, summieren sich aber ständig, und all die kleinen Unterschiede gegenüber früher fordern dauerhaft Aufmerksamkeit
    • Als Gegenbeispiel wird RUSTs Epochs-Update-Modell genannt, das eine Rückwärtskompatibilitätszusage für stabile Features festschreibt und absichtliche Breaking Changes hinter Epochs verbirgt

Stärken von Haskell, die weiterhin fehlen

  • Haskells algebraische Art, Code zu schreiben und zu refaktorieren, bleibt eine Stärke
    • Rust teilt viele Vorteile mit Haskell, aber das Vertrauen, dass kleine mechanische Refactorings die Bedeutung des Programms erhalten, ist dort nicht so leicht zu gewinnen wie in Haskell
  • Auch das Typsystem bietet weiterhin Fähigkeiten, die andere Sprachen in der Regel nicht haben
    • Mainstream-Sprachen des Jahres 2023 haben sich bei statischen Typen gegenüber 2009 stark weiterentwickelt
    • Dennoch bleiben Abstraktionen über higher-kinded types hinaus ein klarer Unterschied von Haskell
  • Haskells Bibliotheksökosystem ist gemischt
    • halb gepflegte Open-Source-Bibliotheken
    • kleine Königreiche einzelner Autoren, die die Welt jeweils auf ihre Weise neu ordnen wollen
    • notwendige, aber untereinander verschiedene string-ähnliche Typen
  • Trotzdem gibt es in Haskell hervorragende Bibliotheken und Abstraktionen, die in anderen Sprachen nur halb so gut möglich sind
    • Lenses sind eine mächtige Abstraktion, deren Vorteile man oft erst nach längerer Nutzung wirklich versteht
    • Brick bietet als deklarative Bausteine eine sehr gute Bibliothek für Terminal-UIs
    • diagrams und music-suite sind ebenfalls gute Beispiele für deklarative APIs
  • Auch die do-Notation wird stark vermisst
    • Entscheidend ist weniger do selbst als eine Syntax, bei der das Hinzufügen weiterer Bindings nicht zu immer tieferer Einrückung führt
    • Verschachteltes flat_map in Ruby wird mit jeder zusätzlichen Achse tiefer eingerückt
    • Haskells do-Notation drückt dieselbe Komposition flach aus
  • Die do-Notation erlaubt es außerdem, strukturelle Nebenläufigkeit im Stil von async/await, konkurrierende Transaktionen und probabilistische Programme mit derselben leichten Syntax einzubetten

Sollte man Haskell lernen oder verwenden?

  • Die Antwort auf „Sollte ich diese Sprache lernen oder verwenden?“ hängt vom Ziel ab
  • Wenn das Ziel ist, ein besserer Programmierer zu werden, ist Haskell sehr lernenswert
    • Haskell ist eine interessante und leistungsfähige Sprache
    • Die Erfahrung, Haskell zu lernen, wird als klarer Gewinn für die eigenen Programmierfähigkeiten bewertet
  • Wenn das Ziel der praktische Einsatz ist, sollte man vorsichtiger sein
    • Haskells Vorteile sind real
    • Man muss sie aber gegen die Frage abwägen, wie viel Reibung Einzelpersonen oder Organisationen bereit sind zu akzeptieren
    • Es gibt durchaus Einzelpersonen und Organisationen, die aus Haskell genug Nutzen ziehen, damit die genannten kleinen Schmerzen auch klein bleiben
  • Wenn man eine klare Ja-Nein-Antwort erzwingt, lautet sie eher „Verwende Haskell nicht“
    • Würde der Autor eine Engineering-Organisation aufbauen, würde Haskell nicht als Standardsprache festgelegt
    • Der Grund ist, dass sowohl der Wert guter Werkzeuge als auch die Kosten schlechter Werkzeuge bekannt sind
    • Bevorzugt werden Sprachen, die sofort eine stabile Sprachbasis, ein klares Code-Ökosystem und hervorragendes Tooling bieten
  • Dennoch stellt Haskell ausgezeichnete Werkzeuge für Programmsicherheit und Korrektheit bereit
    • In bestimmten Kontexten kann es den Preis schlechter Werkzeuge und Linting gegen unnötige Type Families wert sein
  • Haskell passt persönlich nicht mehr so gut wie früher, aber die Schlussfolgerung bleibt: Wäre Haskell nie in die Welt gekommen, wäre die Welt ärmer

1 Kommentare

 
GN⁺ 2023-08-26
Meinungen auf Hacker News
  • Ich habe Haskell etwa zehn Jahre lang verwendet und bin kürzlich beruflich davon weggekommen; dem ersten Punkt des Autors stimme ich definitiv zu: Die Haskell-Community legt sehr großen Wert auf Lernen.
    Es ist schön, mit neugierigen Menschen zu arbeiten und immer etwas zu lehren und zu lernen zu haben. Aber beim Verwerfen ausprobierter Ideen ist sie eher schwach, sodass sich in professionellen Haskell-Codebases leicht viele Dinge ansammeln, die man „zwar tun kann, aber besser nicht verwenden sollte“, wenn man sie nicht streng pflegt.
    Bei der Toolchain bin ich allerdings anderer Meinung. Es stimmt, dass Haskell-Tools nicht besonders gut sind, aber nachdem ich Python, JS, Java, Rust und Elm verwendet habe, fand ich die meisten Tools ebenfalls schlecht und habe die Haskell-Toolchain eher vermisst. cargo ist hervorragend, aber eher die Ausnahme; ich frage mich, ob Rust diesen Zustand vermeiden kann, wenn es älter geworden ist.

    • Ich habe mehrere Sprachen ausprobiert, aber die Tools von Haskell waren nahezu die schlechtesten. Der Language Server brach häufig mit zufälligen Fehlern, und ich war jedes Mal unsicher, ob ich die Haskell-Versionsverwaltung ghcup oder stack überlassen sollte.
      Es gibt ein Projekt, an dem ich gelegentlich arbeite, und ich bin zu 100 % sicher, dass es beim nächsten Öffnen wieder nicht laufen wird. Pythons Verwaltung von Abhängigkeiten und virtuellen Umgebungen ist ähnlich schlecht, aber Poetry war ziemlich gut; bei Ruby funktioniert es ähnlich gut, egal ob man chruby/rbenv/rvm nutzt, und Bundler läuft ebenfalls ordentlich. Java hat zwar seine Schwächen, aber die IDE-Erfahrung ist auf höchstem Niveau, und Refactoring ist zwar nicht so sicher wie in Haskell, lässt sich aber sehr leicht durchführen.
    • Cargo hat sich aus älteren Ideen wie Rubys Bundler entwickelt, daher hat diese Art von Tool eine ziemlich lange Abstammung.
      Bundler ist im Großen und Ganzen nicht schlecht, und auch wenn es Designentscheidungen gibt, denen ich nicht zustimme, passt vieles gut zusammen, wenn man dem vorgesehenen Weg folgt. Sprach-Tools lassen sich ebenfalls ausreichend wenig kaputt bauen; ich habe cargo selbst nicht sehr viel benutzt, aber wenn es wirklich Best-in-Class ist, wäre ich nicht überrascht, wenn es diesen Zustand beibehält.
    • Dass Java-Tools in die Kategorie „nicht besonders gut“ fallen sollen, ist etwas schwer zu glauben. Über Java selbst kann man vieles Negatives sagen, aber die kostenlosen und kommerziellen Tools sind unerreicht.
    • Wegen der Haskell-Tools habe ich die Lust verloren, mich wieder ernsthaft damit zu beschäftigen. Purescript dagegen bot mir jedes Mal, wenn ich ein paar Dinge damit ausprobierte, eine ziemlich reibungslose Erfahrung, besonders wenn man mit JS-Tools vertraut ist.
    • Ich frage mich, warum .NET-Sprachen oder Swift in der Tool-Diskussion fehlen. Vielleicht liegt es daran, dass IDEs wie Visual Studio oder Xcode First-Class-Tools sind, oder daran, dass die Open-Source-Welt dazu neigt, Abhängigkeiten von Big Tech zu vermeiden.
  • Aus Tooling-Sicht hat Haskell meines Wissens eine Sache, mit der andere Sprachen kaum konkurrieren können: Hoogle.
    Hoogle ist erstaunlich gut. Man sagt in Haskell, was man will, und es sagt einem, was man in Haskell tun kann. In Rust gab es ähnliche Versuche, und ich habe auch versucht, Noogle für Nim zu bauen, aber in Sprachen, in denen „Argumente an eine Funktion übergeben“ und „eine Funktion aufrufen“ klar getrennt sind, funktioniert es nicht auf dieselbe Weise.
    Jedes Mal, wenn ich Rust-Code mit verschachtelten Result und Box sehe, wünsche ich mir ein Roogle, das so nützlich ist wie Hoogle. Sonst haben Haskell-Tools keine Vorteile. Zu einem großen Teil scheint das am Instinkt der Community zu liegen, lieber etwas „Innovatives“ zu bauen, statt Bestehendes zu verbessern.

    • Ich stimme stark zu, dass Hoogle enorm nützlich ist. Was die anderen Tools angeht, nutze ich allerdings täglich haskell-language-server, und meine Produktivität ist deutlich besser geworden.
      Perfekt ist er nicht, aber im Vergleich zu vor fünf Jahren ist er viel besser.
    • Hoogle ist wirklich großartig. Davon inspiriert habe ich etwas Ähnliches auch für die C++-Functional-Programming-Bibliothek FunctionalPlus implementiert: https://www.editgym.com/fplus-api-search/
      Ich würde mir wünschen, dass mehr Projekte in diese Richtung gehen.
  • Ich sehe das größte Problem darin, dass GHC an eine bestimmte Version von base, der Standardbibliothek, gebunden ist.
    Wenn sich base ändert und eine neue GHC-Version nur diese Version unterstützt, muss man praktisch alle Abhängigkeiten ändern, um einen neueren GHC zu verwenden. Ich verstehe immer noch nicht, warum mit GHC 9.6 kompilierter Code base 4.18.0.0 verwenden muss. Warum sollte sich das GHC-Binary für die Version des Moduls Data.List im zu kompilierenden Code interessieren?
    Ich verstehe, dass GHC-spezifische Elemente, die base bereitstellt, an eine bestimmte GHC-Version gebunden sind, aber ich weiß nicht, warum das auch für den Rest gelten muss. Soweit ich weiß, läuft allerdings Arbeit daran, base in mehrere Pakete aufzuteilen, um das zu beheben: https://gitlab.haskell.org/ghc/ghc-wiki-mirror/-/blob/master...

    • In der zitierten Stelle steckt ein Hinweis. Ein neuerer GHC könnte alten base-Code als ungültig ablehnen.
    • Weil interne Datentypen unterschiedlich sein können. Wenn Bibliotheken, die gegen verschiedene base-Implementierungen gelinkt sind, Data.List-Instanzen austauschen, entsteht ein Problem.
      Wenn es zum Beispiel eine Liste aus Data.List gibt: Könnte man dann die Ergebnisse zweier Funktionen aus unterschiedlichen Bibliotheken an diese Liste anhängen?
    • Wie angemerkt, ist eine wiederinstallierbare base das Ziel, das alle wollen. Die jetzige Situation hat vor allem historische Gründe und liegt daran, dass grundlegende Elemente, die intern an den Compiler gebunden sind, mit nicht-grundlegenden Elementen gekoppelt wurden; mit genügend Aufwand kann das verbessert werden.
  • Wenn es drei Hauptgründe gab, warum mein Interesse an Haskell nach und nach abgekühlt ist, dann waren es eine stilistische Vorliebe für das Neue, die obskuren Code feiert und Wartung erschwert, unbeholfene Tools, die Alltagsarbeit zäher machen, sowie fortlaufende Veränderungen, die zwar nur gelegentlich, aber doch dauerhaft Aufmerksamkeit erfordern und regelmäßig Dinge kaputtmachen.
    Das ist ein berechtigter Punkt. Ich habe 2010–2012 viel Haskell gelernt; die Sprache selbst war hervorragend, aber Dokumentation und Tools waren schwer zu handhaben. Die Community ging von Cabal und der berüchtigten Cabal Hell zu Stack und dann wieder zurück zu Cabal, und insgesamt ist es besser geworden.
    Zugleich haben auch andere Programmiersprachen Elemente funktionaler Programmierung aufgenommen. Java hat zum Beispiel Streams, Funktionen, Lambdas, algebraische Datentypen, Records und Pattern Matching hinzugefügt; auch wenn die Syntax nicht so elegant ist wie bei Haskell, sind die Grundkonzepte funktionaler Programmierung enthalten.

    • In ihrem verzweifelten Versuch, dem Gefängnis zu entkommen, übernahmen die Gefangenen bizarre Rituale, inspiriert vom Licht, das sie zwischen den engen Gitterstäben sahen, und beschmutzten damit ihre Sprache.
      So schufen sie sich selbst eine endlose Grube des Leidens, die nur dadurch gerade noch erträglich wurde, dass die Lichtstrahlen nicht tief genug fielen, um die Farben zu zeigen, die das Licht in den Bäumen vor dem Fenster erzeugte. Wer von draußen kam, wurde zwischen einem Dialekt, der an ihre selbstgeschaffene Dunkelheit angepasst war, und einem Dialekt, der im Licht gedeihen konnte, dort aber erstickte, zerrissen und verlor rasch den Verstand.
    • Effiziente unveränderliche Datenstrukturen sind für funktionale Programmierung wichtiger als die meisten der aufgezählten Features. Was bietet Java da?
    • Ich wusste nicht, dass es von Cabal zu Stack und dann wieder zurück zu Cabal ging. Ich war ein paar Jahre von Haskell weg, und als ich es zuletzt benutzt habe, wirkte Stack am reibungslosesten und so, als hätte es viele der Schmerzen von Cabal gelöst.
      Wenn die Community wieder zu Cabal zurückgekehrt ist, frage ich mich, was ich verpasst habe.
  • Aus diesen Gründen bin ich zu F# gewechselt und habe kaum zurückgeblickt. Manchmal vermisse ich höhere Kind-Typen, aber F# hat auch Generics, und ehrlich gesagt zwingt es einen in gewisser Weise dazu, einfacheren Code zu schreiben als in Haskell, was im Großen und Ganzen zu besseren Ergebnissen führt.
    Allerdings vermittelt F# nicht dieses „Könnergefühl“ wie Haskell. Es ist Haskell-ähnlich, aber durch und durch pragmatisch. Ich erledige viel mit F# und nutze es gern, aber manchmal blicke ich etwas wehmütig zurück und frage mich, warum es mit Haskell nicht geklappt hat.

    • Schön, so etwas zu lesen. Ich bin neu in der funktionalen Programmierung und habe Spaß an F#; mir gefällt, dass es knapp und praxisorientiert ist.
      Ich habe mich oft gefragt, ob ich etwas verpasse, wenn ich Haskell nicht nutze, aber für meine Zwecke scheint das wahrscheinlich nicht der Fall zu sein.
  • Haskell zu lernen fühlt sich an, als würde man eine außerirdische Sprache entschlüsseln. Wenn man an gewöhnliche if-else-Schleifen und einfache Variablenzuweisungen gewöhnt ist, sollte man sich darauf einstellen, dass sich der Kopf verknotet.
    Eine monad ist kein Weltraummonster, sondern ein seltsames abstraktes Konzept, das einen dazu bringen kann, sich am Kopf zu kratzen und die eigenen Lebensentscheidungen zu hinterfragen. Auch bei den Bibliotheken fühlt es sich an, als würde man in einem halb leeren Secondhandladen wühlen, um das zu finden, was man braucht, und die Dokumentation liest sich zur Hälfte wie Hieroglyphen.
    Auch bei der Performance kann man in der Praxis wegen Lazy Evaluation lange darüber grübeln, warum mehr Speicher als erwartet verbraucht wird und alles langsamer wird. Jobs sind ebenfalls schwer zu finden, außer bei sehr nischigen Firmen, die voll auf Haskell setzen, und Debugging fühlt sich an wie die Suche nach einer Nadel in einem Haufen funktionaler Hieroglyphen.

  • Ich habe Scala verwendet und denke, dass es das Beste aus beiden Welten bieten kann. Man kann algebraisch darüber nachdenken, und wenn es nach einem Refactoring kompiliert, funktioniert es meistens auch.
    Typinferenz und monad funktionieren ebenfalls gut, und man bekommt die Vorteile des Java-Ökosystems. Wenn es zu obskur wird, kann man die Regeln brechen und näher an Java programmieren. Mich würde interessieren, was andere dazu denken.

    • Aus denselben Gründen habe ich Scala wirklich gern genutzt. Es gibt einen Ausweg, wenn man ihn braucht, und das Ökosystem ist groß.
      Allerdings ist sbt zum Verrücktwerden und wahrscheinlich der Teil der Developer Experience, der mir am wenigsten gefällt.
    • Zustimmung. Mir gefällt auch, dass es einen sanften, schrittweisen Übergang von imperativem/OOP-Code zu einem rein funktionalen Stil ermöglicht.
  • Die Leitung und die Maintainer von Haskell scheinen tatsächlich zu wollen, dass diese Sprache Forschung, Experimenten und akademischer Erkundung gegenüber aufgeschlossen ist.
    Das ist an sich in Ordnung, aber irgendwann muss es zwangsläufig mit den Interessen von Programmierern kollidieren, die Haskell als praktisches und stabiles Werkzeug nutzen wollen. Nötig scheint die Fähigkeit, Experimentelles und Cutting-Edge-Dinge als separaten Track zu kennzeichnen. Ob per Pragma oder Deklaration auf Paketebene: Schon am Paket sollte erkennbar sein, ob man es hereinziehen will.
    Dann könnten praxisorientierte Entwickler Richtlinien wie „Wir verwenden nur stable Haskell“ übernehmen. Man kann die Dynamik von „Avoid success at all costs“ anerkennen, wonach eine zu hohe Haskell-Adoption die Sprache in Trägheit gefangen halten und daran hindern könnte, Cutting-Edge-Konzepte voranzutreiben; trotzdem braucht es einen offiziellen Track, auf dem Alltagsentwickler bei einer stärker eingeschränkten und stabileren Teilmenge von Haskell bleiben können.

    • Genau. Dieses Prinzip ist in A History of Haskell: Being Lazy with Class (2007) als Kernprinzip festgehalten. Direkter PDF-Link: https://www.microsoft.com/en-us/research/wp-content/uploads/...
    • Diese Rolle übernimmt Stackage. Stackage stellt eine konsistente Menge von Haskell-Paketen bereit, von denen bekannt ist, dass sie gemeinsam gebaut werden und Tests bestehen; daraus werden Stackage-Nightly-Snapshots und LTS-Releases (Long Term Support).
      Java wird so etwas nie bekommen. https://www.stackage.org/
    • Es gibt die Simple Haskell-Initiative, die eine solche Richtung fördert. Allerdings gibt es kein Flag und kein Pragma, mit dem man sagen könnte: „Dieses Projekt verwendet simple Haskell.“
      Einfachheit liegt am Ende im Auge des Betrachters. Auch ausgefallene Typ-Features haben ihren Nutzen: Wenn man ihre Funktionsweise verinnerlicht hat, können sie Typen ausdrucksstärker machen und Code sicherer und sogar einfacher machen.
  • Ein ziemlich guter Artikel. Der schwache Punkt ist allerdings, dass ich nie den Druck verspürt habe, ein bestimmtes neues, glänzendes Type-Feature zu verwenden, wenn ich das nicht wollte.
    Wenn einem Servants HTTP-API auf Type-Level nicht gefällt, kann man eine Ebene tiefer gehen und warp verwenden. Die Libraries verstehen, dass übermäßig komplexe Typen zur Falle werden können, und sind deshalb oft auf diese Weise geschichtet. Man braucht ein Gespür dafür, wie weit man gehen sollte, und zu diesem Prozess gehören auch Fehler.

    • Ich frage mich, ob jemand einen guten „Bitte bringt mir das bei“-Artikel für Leute kennt, die Haskell nicht gut kennen, denen die Idee von Servant aber unglaublich gut erscheint.
      Ich habe die Servant-Dokumentation gelesen, aber sie setzte – wenig überraschend – mehr Haskell-Verständnis voraus, als ich habe.
  • Dieser Artikel trifft bei mir wirklich einen Nerv. Ich verwende seit etwa 1,5 Jahren Haskell in einer ziemlich industriellen Anwendung und hatte auch davor schon einiges an Erfahrung, etwa mit https://github.com/mattgreen/hython.
    Zum ersten Mal seit Langem in meiner 20-jährigen Laufbahn musste ich wieder sehr viel lernen; das ist das Ergebnis aus Domäne und Sprache zusammen. Es ist aufregend, aber auch ermüdend, und zusammen mit einem vollen Familienleben kann das ein ziemlich großer Brocken sein.
    Trotzdem ist die Sprache hervorragend. Mein Manager empfiehlt meist, sie top-down zu verwenden: Weil Haskells Flexibilität die Entwicklungsabsicht gut vermitteln kann, soll man gründlich darüber nachdenken, wie etwas gelesen werden soll, und dort anfangen. Das ist ein großer mentaler Wechsel gegenüber den meisten Sprachen, in denen man wiederholt function tippt und das Gehirn in den Energiesparmodus geht; Haskell wirkt wirklich schreibfreundlich entworfen.
    Aber selbst wenn man kontrollierte Effekte mit Monads behandelt, bleibt die Syntax schwer zu überfliegen. Vielleicht liegt es daran, dass eine Zeile sehr dicht werden kann. Ich vermisse Early Returns sehr, und auch Rusts hervorragenden LSP. Wegen interner Änderungen in Haskell 9.x hat Haskell zuletzt sogar die Funktion verloren, Pattern Matches auszufüllen; Monads zu stapeln mag ich weiterhin nicht und mache dabei oft Fehler. Die Compile-Zeiten können hart sein, besonders wenn man auf die lens-Library trifft.
    Auch die standardmäßige Lazy Evaluation gefällt mir nicht. Die Community erkennt inzwischen bis zu einem gewissen Grad an, dass Haskell-Programme wegen dieses Defaults deutlich anfälliger für Space Leaks sind und dass diese in vielen Programmen ziemlich lange unentdeckt bleiben können. Als Systemprogrammierer möchte man da schreien.
    Trotzdem ist die Community eine der stärksten Gruppen von Programmierern, die ich je gesehen habe. Ich will nicht einfach das Meme vom besonders schlauen Haskell-Entwickler bedienen; entscheidend ist, dass sie wirklich tief nachdenken und die Dinge vorantreiben. Für unsere Domäne, Programmanalyse, passt Haskell wie angegossen, aber außerhalb davon bin ich weiterhin etwas vorsichtig. Im Grunde dürste ich immer noch nach einer Programmiersprache, die OCaml ist, aber mit besserer Syntax.

    • Für die Lesbarkeit braucht man manchmal unbedingt Early Returns. In solchen Fällen würde ich ExceptT empfehlen: https://www.stackage.org/haddock/lts-21.8/mtl-2.2.2/Control-...
      Man muss ExceptT auch nicht im Interface offenlegen; man kann es intern nur für Early Returns verwenden und die Funktion dann Either zurückgeben lassen.
    • Mit dem Wunsch nach „OCaml, aber mit besserer Syntax“ bist du nicht allein. Mir geht es genauso, und deshalb habe ich mich tief in Reason eingearbeitet, als es erstmals erschien.
      Schade, dass es am Ende keine große Traktion bekommen hat.
    • Das ist Scala 3. Es hat eine Python-ähnliche Syntax und dank der JVM ein deutlich größeres Library-Ökosystem als Haskell oder OCaml.
      Es integriert objektorientierte und funktionale Programmierung besser als OCaml, und idiomatischer OCaml-Code ist ähnlich genug, dass er sich mit einfachen Transformationen leicht übertragen lässt.