Wie wichtig sind Haskells fortschrittliche Konzepte wie Monaden und Applikative Funktoren für die meisten Routine-Programmieraufgaben?

16

Ich habe das Buch Learn You a Haskell bis zu dem Punkt gelesen, an dem sie Monaden und ähnliches vorstellen. Diese sind für mich so eingängig, dass ich einfach nicht mehr versuchen möchte, sie zu lernen.

Aber ich möchte es wirklich versuchen.

Ich frage mich, ob ich zumindest die fortgeschrittenen Konzepte für eine Weile vermeiden kann und erst anfange, die anderen Teile der Sprache zu verwenden, um eine Menge praktischer Aufgaben mit den grundlegenderen Teilen von Haskell zu erledigen, dh Funktionen, Standard-E / A, Dateihandhabung, Datenbankanbindung und dergleichen.

Ist dies ein vernünftiger Ansatz, um zu lernen, wie man Haskell benutzt?

Dan
quelle
2
Ich weiß nicht, wie weit du kommen wirst. Ohne Monaden können Sie in Haskell nichts Nützliches tun, da für die Programmierung (wie das Schreiben eines seriösen Programms, das jemand wirklich verwenden möchte) E / A und Status erforderlich sind, die beide nur in Haskell verwaltet werden können durch die Verwendung von Monaden.
Mason Wheeler
1
@dan - Endlich habe ich Haskell-Monaden verstanden, indem ich zwei Dokumente gelesen habe: "Du hättest Monaden erfinden können" und "Die unangenehme Truppe bekämpfen". Ich kann nicht sagen, ob sie besser sind als "Learn You a Haskell", das ich nicht gelesen habe, aber sie haben für mich gearbeitet. Um die Do-Notation mit der E / A-Monade zu verwenden, müssen Sie nur sehr wenig verstehen, was Monaden sind. Sie werden einige Dinge tun, die die Experten entweder zum Lachen oder zum Weinen bringen eine konventionelle imperative Sprache. Aber es ist die Mühe wert etwas tieferes Verständnis zu bekommen.
Steve314
2
@dan, es hat fast ein Jahrzehnt gedauert, bis ich von OOP gelangweilt wurde und anfing, funktionale Sprachen zu schätzen / neugierig zu sein. Ich würde immer noch F # und Clojure lernen, bevor ich Haskell ernsthaft versuche. Während persönliche Herausforderungen nett sind, gibt es etwas zu sagen über Ihr Bauchgefühl und den Weg des geringsten Widerstands. Viel hängt davon ab, wer Sie sind. Wenn Sie ein stark mathematischer Typ sind, würden Sie wahrscheinlich Haskell / Schem von Anfang an mögen. Wenn Sie eher der Typ sind, der es schafft, ist es auch in Ordnung. Nähern Sie sich Haskell nicht mit einer Do or Die-Haltung. Lerne weiter andere Dinge und wenn dir langweilig ist, komm zurück.
Job
Danke für den Rat @Job. Ich habe Clojure ein bisschen ausprobiert, aber die Lisp-Syntax funktioniert bei mir nicht, sowohl beim Lesen als auch beim Schreiben. Ich finde Haskells Syntax natürlicher. Ich benutze Ruby in der Zwischenzeit gerne für Projekte, hoffe aber, einige praktische Projekte in Haskell durchführen zu können.
Dan
@dan, witzig, ich finde die Syntax von Clojure ziemlich freundlich, aber das liegt wahrscheinlich daran, dass ich vorher Scheme ausgesetzt war. Vielleicht gewöhne ich mich nach F # an die Art und Weise, wie Haskell Dinge tut.
Job

Antworten:

16

Lassen Sie uns zunächst unterscheiden zwischen dem Erlernen der abstrakten Konzepte und dem Erlernen spezifischer Beispiele .

Sie werden nicht allzu weit kommen, wenn Sie alle spezifischen Beispiele ignorieren, und das aus dem einfachen Grund, weil sie absolut allgegenwärtig sind. Tatsächlich existieren die Abstraktionen zu einem großen Teil, weil sie die Dinge, die Sie sowieso tun würden, mit den spezifischen Beispielen vereinen.

Die Abstraktionen selbst sind zwar nützlich , aber nicht sofort notwendig. Man kann ziemlich weit kommen, wenn man die Abstraktionen komplett ignoriert und einfach die verschiedenen Typen direkt verwendet. Sie werden sie irgendwann verstehen wollen, aber Sie können später immer noch darauf zurückkommen. Tatsächlich kann ich fast garantieren, dass Sie sich in diesem Fall die Stirn schlagen und sich fragen, warum Sie die ganze Zeit auf die harte Tour verbracht haben, anstatt die praktischen Allzweckwerkzeuge zu verwenden.

Nehmen Sie Maybe aals Beispiel. Es ist nur ein Datentyp:

data Maybe a = Just a | Nothing

Es ist alles andere als selbstdokumentierend; Es ist ein optionaler Wert. Entweder Sie haben "nur" etwas von Typ a, oder Sie haben nichts. Angenommen, Sie haben eine Art Nachschlagefunktion, die darauf hinweist Maybe String, Stringdass ein möglicherweise nicht vorhandener Wert gesucht wird . Sie stimmen also mit dem Wert überein, um festzustellen, um welchen Wert es sich handelt:

case lookupFunc key of
    Just val -> ...
    Nothing  -> ...

Das ist alles!

Wirklich, sonst brauchen Sie nichts. Kein Functors oder Monads oder irgendetwas anderes. Diese drücken übliche Arten der Verwendung von Maybe aWerten aus ... aber sie sind nur Redewendungen, "Entwurfsmuster", wie auch immer Sie es nennen möchten.

Der einzige Ort, an dem Sie es wirklich nicht ganz vermeiden können, ist mit IO, aber das ist sowieso eine mysteriöse Black Box. Es lohnt sich also nicht zu versuchen, zu verstehen, was es bedeutet, als Monadwas oder was auch immer.

In der Tat, hier ist ein Spickzettel für alles, was Sie für den Moment wirklich wissen müssen IO:

  • Wenn etwas einen Typ IO ahat, bedeutet das, dass es eine Prozedur ist , die etwas tut und einen aWert ausspuckt .

  • Wenn Sie einen Codeblock mit der doNotation haben, schreiben Sie so etwas:

    do -- ...
       inp <- getLine
       -- etc...
    

    ... bedeutet , die Prozedur rechts von auszuführen<- und das Ergebnis dem Namen links zuzuweisen.

  • Wenn Sie so etwas haben:

    do -- ...
       let x = [foo, bar]
       -- etc...
    

    ... bedeutet, den Wert des einfachen Ausdrucks (keine Prozedur) rechts von =dem Namen links zuzuweisen .

  • Wenn Sie etwas dort ablegen, ohne einen Wert zuzuweisen:

    do putStrLn "blah blah, fishcakes"
    

    ... bedeutet, eine Prozedur auszuführen und alles zu ignorieren, was sie zurückgibt. Einige Prozeduren haben den Typ IO ()- der ()Typ ist eine Art Platzhalter, der nichts aussagt , was bedeutet, dass die Prozedur etwas tut und keinen Wert zurückgibt . So ähnlich wie eine voidFunktion in anderen Sprachen.

  • Wenn Sie den gleichen Vorgang mehrmals ausführen, kann dies zu unterschiedlichen Ergebnissen führen. das ist so eine idee. Aus diesem Grund gibt es keine Möglichkeit, das IOaus einem Wert zu "entfernen" , da etwas in IOkein Wert ist, sondern eine Prozedur, um einen Wert zu erhalten.

  • Die letzte Zeile in einem doBlock muss eine einfache Prozedur ohne Zuweisung sein, wobei der Rückgabewert dieser Prozedur der Rückgabewert für den gesamten Block wird. Wenn der Rückgabewert einen bereits zugewiesenen Wert verwenden soll, nimmt die returnFunktion einen einfachen Wert und gibt Ihnen eine No-Op-Prozedur, die diesen Wert zurückgibt.

  • Davon abgesehen hat es nichts Besonderes IO. Diese Prozeduren sind eigentlich reine Werte, und Sie können sie weitergeben und auf verschiedene Arten kombinieren. Nur wenn sie in einem doBlock ausgeführt werden, der von irgendwoher aufgerufen wird main, tun sie etwas.

In so etwas wie diesem äußerst langweiligen, stereotypen Beispielprogramm:

hello = do putStrLn "What's your name?"
           name <- getLine
           let msg = "Hi, " ++ name ++ "!"
           putStrLn msg
           return name

... Sie können es wie ein Imperativprogramm ablesen. Wir definieren eine Prozedur mit dem Namen hello. Bei der Ausführung wird zuerst eine Prozedur zum Drucken einer Nachricht ausgeführt, in der Sie nach Ihrem Namen gefragt werden. Als nächstes führt es eine Prozedur aus, die eine Eingabezeile liest und das Ergebnis zuweist name. dann weist es dem Namen einen Ausdruck zu msg; dann druckt es die Nachricht aus; Dann wird der Name des Benutzers als Ergebnis des gesamten Blocks zurückgegeben. Da a nameist String, bedeutet dies, dass helloes sich um eine Prozedur handelt, die a zurückgibt String, also einen Typ hat IO String. Und jetzt können Sie diese Prozedur an einer anderen Stelle ausführen, genau wie sie ausgeführt wird getLine.

Pfff, Monaden. Wer braucht sie?

CA McCann
quelle
2
@dan: Gern geschehen! Wenn Sie unterwegs spezielle Fragen haben, wenden Sie sich bitte an Stack Overflow. Das [haskell] -Tag ist dort normalerweise ziemlich aktiv, und wenn Sie erklären, woher Sie kommen, können die Leute Ihnen ziemlich gut helfen, Verständnis zu erlangen, anstatt nur kryptische Antworten zu werfen.
CA McCann
9

Wie wichtig sind Haskells fortschrittliche Konzepte wie Monaden und Applikative Funktoren für die meisten Routine-Programmieraufgaben?

Sie sind wesentlich. Ohne sie ist es nur allzu einfach, Haskell als eine weitere imperative Sprache zu verwenden, und obwohl Simon Peyton Jones Haskell einmal als "die beste imperative Programmiersprache der Welt" bezeichnet hat, verpassen Sie immer noch das Beste.

Monaden, Monadentransformationen, Monoide, Funktoren, anwendbare Funktoren, entgegengesetzte Funktoren, Pfeile, Kategorien usw. sind Entwurfsmuster. Sobald Sie die spezifische Sache, die Sie machen, in eine von ihnen eingepasst haben, können Sie auf eine Fülle von Funktionen zurückgreifen, die abstrakt geschrieben sind. Diese Typenklassen sind nicht nur dazu da, Sie zu verwirren, sie sind dazu da, Ihnen das Leben zu erleichtern und Sie produktiver zu machen. Sie sind meiner Meinung nach der größte Grund für die Prägnanz eines guten Haskell-Codes.

dan_waterworth
quelle
1
+1: Vielen Dank für den Hinweis, dass "Monaden, Monadentransformationen, Monoide, Funktoren, anwendbare Funktoren, kontravariante Funktoren, Pfeile, Kategorien usw. Entwurfsmuster sind." !
Giorgio
7

Ist es unbedingt notwendig, wenn Sie nur etwas zum Laufen bringen wollen? Nein.

Ist es notwendig, wenn Sie schöne idiomatische Haskell-Programme schreiben möchten? Absolut.

Bei der Programmierung in Haskell ist es hilfreich, zwei Dinge zu beachten:

  1. Das Typensystem hilft Ihnen dabei. Wenn Sie Ihr Programm aus hochpolymorphem, typenklassenintensivem Code zusammensetzen, können Sie sehr oft in die wunderbare Situation geraten, in der der Typ der gewünschten Funktion Sie zu der (einen einzigen!) Korrekten Implementierung führt .

  2. Die verschiedenen verfügbaren Bibliotheken wurden unter Verwendung des Prinzips # 1 entwickelt.

Wenn ich also ein Programm in Haskell schreibe, beginne ich damit, die Typen zu schreiben. Dann beginne ich von vorne und schreibe einige allgemeinere Typen auf (und wenn es sich um Instanzen bestehender Typenklassen handelt, umso besser). Dann schreibe ich einige Funktionen, die mir Werte der Typen geben, die ich will. (Dann spiele ich mit ihnen ghciund schreibe vielleicht ein paar QuickCheck-Tests.) Und schließlich klebe ich alles zusammen main.

Es ist möglich, hässliches Haskell zu schreiben, das gerade etwas zum Laufen bringt. Aber wenn Sie diese Phase erreicht haben, gibt es noch so viel zu lernen.

Viel Glück!

Lambdageek
quelle
1
Vielen Dank! Ich habe zwischen Clojure und Haskell geschwankt, aber jetzt neige ich mich zu Haskell, weil Leute wie Sie so hilfsbereit waren.
Dan
1
Die Haskell-Community scheint sehr nett und hilfsbereit zu sein. Und da scheinen viele interessante Ideen zu herkommen
Zachary K