Warum sollte ich mich als Praktizierender für Haskell interessieren? Was ist eine Monade und warum brauche ich sie? [geschlossen]


9

Ich verstehe einfach nicht, welches Problem sie lösen.



2
Ich denke, diese Bearbeitung ist etwas extrem. Ich denke, Ihre Frage war im Wesentlichen gut. Es ist nur so, dass einige Teile davon ein bisschen ... argumentativ waren. Was wahrscheinlich nur das Ergebnis der Frustration ist, etwas zu lernen, von dem Sie einfach nicht verstanden haben.
Jason Baker

@ SnOrfus, ich war derjenige, der die Frage bastardisiert hat. Ich war zu faul, um es richtig zu bearbeiten.
Job

Antworten:


34

Monaden sind weder gut noch schlecht. Sie sind es einfach. Sie sind Werkzeuge, mit denen Probleme wie mit vielen anderen Konstrukten von Programmiersprachen gelöst werden können. Eine sehr wichtige Anwendung ist es, Programmierern, die in einer rein funktionalen Sprache arbeiten, das Leben zu erleichtern. Sie sind jedoch in nicht funktionierenden Sprachen nützlich. Es ist nur so, dass die Leute selten merken, dass sie eine Monade benutzen.

Was ist eine Monade? Die beste Art, sich eine Monade vorzustellen, ist ein Designmuster. Im Fall von E / A könnte man sich wahrscheinlich kaum mehr als eine verherrlichte Pipeline vorstellen, in der der globale Zustand zwischen den Phasen weitergegeben wird.

Nehmen wir zum Beispiel den Code, den Sie schreiben:

do
  putStrLn "What is your name?"
  name <- getLine
  putStrLn ("Nice to meet you, " ++ name ++ "!")

Hier ist viel mehr los als man denkt. Sie werden beispielsweise feststellen, dass putStrLndie folgende Signatur vorliegt : putStrLn :: String -> IO (). Warum ist das?

Stellen Sie sich das so vor: Stellen wir uns der Einfachheit halber vor, dass stdout und stdin die einzigen Dateien sind, in die wir lesen und schreiben können. In einer imperativen Sprache ist dies kein Problem. In einer funktionalen Sprache können Sie den globalen Status jedoch nicht mutieren. Eine Funktion ist einfach etwas, das einen Wert (oder Werte) annimmt und einen Wert (oder Werte) zurückgibt. Eine Möglichkeit, dies zu umgehen, besteht darin, den globalen Status als Wert zu verwenden, der an jede Funktion übergeben wird. Sie können also die erste Codezeile in etwa Folgendes übersetzen:

global_state <- (\(stdin, stdout) -> (stdin, stdout ++ "What is your name?")) global_state

... und der Compiler würde alles drucken müssen, was dem zweiten Element von hinzugefügt wurde global_state. Jetzt weiß ich nichts über dich, aber ich würde es hassen, so zu programmieren. Die Art und Weise, wie dies erleichtert wurde, war die Verwendung von Monaden. In einer Monade übergeben Sie einen Wert, der einen Zustand von einer Aktion zur nächsten darstellt. Aus diesem Grund putStrLngibt es einen Rückgabetyp IO (): Er gibt den neuen globalen Status zurück.

Warum kümmert es dich? Nun, die Vorteile der funktionalen Programmierung gegenüber dem imperativen Programm wurden an mehreren Stellen zu Tode diskutiert, daher werde ich diese Frage im Allgemeinen nicht beantworten (aber lesen Sie dieses Dokument, wenn Sie den Fall der funktionalen Programmierung hören möchten). Für diesen speziellen Fall kann es jedoch hilfreich sein, wenn Sie verstehen, was Haskell zu erreichen versucht.

Viele Programmierer sind der Meinung, dass Haskell versucht, sie daran zu hindern, imperativen Code zu schreiben oder Nebenwirkungen zu verwenden. Das stimmt nicht ganz. Stellen Sie sich das so vor: Eine imperative Sprache ist eine Sprache, die standardmäßig Nebenwirkungen zulässt, aber es Ihnen ermöglicht, Funktionscode zu schreiben, wenn Sie dies wirklich möchten (und bereit sind, mit einigen der erforderlichen Verzerrungen umzugehen). Haskell ist standardmäßig rein funktional, ermöglicht es Ihnen jedoch, imperativen Code zu schreiben, wenn Sie dies wirklich möchten (was Sie tun, wenn Ihr Programm nützlich sein soll). Es geht nicht darum, das Schreiben von Code mit Nebenwirkungen zu erschweren. Es soll sicherstellen, dass Sie explizit über Nebenwirkungen informiert sind (wobei das Typsystem dies erzwingt).


6
Dieser letzte Absatz ist Gold. Um es ein wenig zu extrahieren und zu paraphrasieren: "Eine imperative Sprache erlaubt standardmäßig Nebenwirkungen, aber Sie können Funktionscode schreiben, wenn Sie wirklich wollen. Eine funktionale Sprache ist standardmäßig rein funktional, aber Sie können imperativen Code schreiben wenn du wirklich willst. "
Frank Shearar

Es ist erwähnenswert, dass das Papier, mit dem Sie verlinkt haben, die Idee der "Unveränderlichkeit als Tugend der funktionalen Programmierung" gleich zu Beginn ausdrücklich ablehnt .
Mason Wheeler

@MasonWheeler: Ich habe diese Absätze nicht als Ablehnung der Bedeutung der Unveränderlichkeit, sondern als überzeugendes Argument für die Demonstration der Überlegenheit der funktionalen Programmierung gelesen . Tatsächlich sagt er dasselbe über die Beseitigung goto(als Argument für strukturierte Programmierung) etwas später in der Arbeit und charakterisiert solche Argumente als "fruchtlos". Und doch wünscht sich keiner von uns heimlich die gotoRückkehr. Es ist einfach so, dass man nicht argumentieren kann, dass gotodies für Leute, die es ausgiebig nutzen , nicht notwendig ist.
Robert Harvey

7

Ich werde beißen!!! Monaden an sich sind für Haskell keine wirkliche Existenzberechtigung (frühe Versionen von Haskell hatten sie nicht einmal).

Ihre Frage ist ein bisschen so, als würde man sagen: "C ++, wenn ich mir die Syntax ansehe, wird mir so langweilig. Aber Vorlagen sind eine stark beworbene Funktion von C ++, also habe ich mir eine Implementierung in einer anderen Sprache angesehen."

Die Entwicklung eines Haskell-Programmierers ist ein Witz, der nicht ernst genommen werden soll.

Eine Monade für den Zweck eines Programms in Haskell ist eine Instanz der Typklasse Monad, dh ein Typ, der zufällig eine bestimmte kleine Menge von Operationen unterstützt. Haskell bietet spezielle Unterstützung für Typen, die die Monad-Typklasse implementieren, insbesondere syntaktische Unterstützung. Praktisch führt dies zu einem sogenannten "programmierbaren Semikolon". Wenn Sie diese Funktionalität mit einigen anderen Funktionen von Haskell kombinieren (erstklassige Funktionen, standardmäßig Faulheit), erhalten Sie die Möglichkeit, bestimmte Dinge als Bibliotheken zu implementieren, die traditionell als Sprachfunktionen angesehen wurden. Sie können beispielsweise einen Ausnahmemechanismus implementieren. Sie können die Unterstützung für Fortsetzungen und Coroutinen als Bibliothek implementieren. Haskell, die Sprache unterstützt keine veränderlichen Variablen:

Sie fragen nach "Vielleicht / Identität / Safe Division Monaden ???". Die Vielleicht-Monade ist ein Beispiel dafür, wie Sie die Ausnahmebehandlung (sehr einfach, nur eine Ausnahme) als Bibliothek implementieren können.

Sie haben Recht, Nachrichten zu schreiben und Benutzereingaben zu lesen ist nicht sehr einzigartig. IO ist ein mieses Beispiel für "Monaden als Merkmal".

Aber um zu iterieren, ein "Feature" für sich (z. B. Monaden), das vom Rest der Sprache isoliert ist, erscheint nicht unbedingt sofort nützlich (ein großartiges neues Feature von C ++ 0x sind rWert-Referenzen, bedeutet nicht, dass Sie es nehmen können sie aus dem Kontext C ++ heraus, weil es Sie Syntax langweilt und unbedingt das Dienstprogramm sehen). Eine Programmiersprache erhalten Sie nicht, wenn Sie eine Reihe von Funktionen in einen Eimer werfen.


Tatsächlich unterstützt haskell veränderbare Variablen über die ST-Monade (einer der wenigen seltsamen unreinen magischen Teile der Sprache, die nach ihren eigenen Regeln gespielt werden).
Sara

4

Programmierer schreiben alle Programme, aber die Ähnlichkeiten enden dort. Ich denke, Programmierer unterscheiden sich weit mehr, als sich die meisten Programmierer vorstellen können. Nehmen Sie an einem langjährigen "Kampf" teil, wie statische Variablentypisierung gegen Nur-Laufzeit-Typen, Skripterstellung gegen Kompilierung, C-Stil gegen objektorientierte. Sie werden es unmöglich finden, rational zu argumentieren, dass ein Lager minderwertig ist, weil einige von ihnen exzellenten Code in einem Programmiersystem produzieren, das mir sinnlos oder sogar geradezu unbrauchbar erscheint.

Ich denke, verschiedene Leute denken einfach anders, und wenn Sie nicht von syntaktischem Zucker oder insbesondere von Abstraktionen verführt werden, die nur der Einfachheit halber existieren und tatsächlich erhebliche Laufzeitkosten verursachen, dann halten Sie sich auf jeden Fall von solchen Sprachen fern.

Ich würde jedoch empfehlen, dass Sie zumindest versuchen, sich mit den Konzepten vertraut zu machen, die Sie aufgeben. Ich habe nichts gegen jemanden, der vehement für C ist, solange er tatsächlich versteht, worum es bei Lambda-Ausdrücken geht. Ich vermute, dass die meisten nicht sofort ein Fan werden, aber zumindest wird es im Hinterkopf sein, wenn sie das perfekte Problem finden, das mit Lambdas so viel einfacher zu lösen gewesen wäre.

Und versuchen Sie vor allem, sich nicht über Fanboys zu ärgern, insbesondere über Leute, die nicht wissen, wovon sie sprechen.


4

Haskell erzwingt referenzielle Transparenz : Bei gleichen Parametern gibt jede Funktion immer das gleiche Ergebnis zurück, unabhängig davon, wie oft Sie diese Funktion aufrufen.

Das bedeutet zum Beispiel, dass Sie auf Haskell (und ohne Monaden) keinen Zufallszahlengenerator implementieren können. In C ++ oder Java können Sie dies mithilfe globaler Variablen tun, indem Sie den Zwischenwert "Startwert" des Zufallsgenerators speichern.

Auf Haskell sind Monaden das Gegenstück zu globalen Variablen.


Also ... was wäre, wenn Sie einen Zufallszahlengenerator wollten? Ist es nicht auch eine Funktion? Auch wenn nicht, wie bekomme ich mir einen Zufallsgenerator?
Job

@Job Sie können einen Zufallszahlengenerator innerhalb einer Monade erstellen (im Grunde genommen ein State-Tracker), oder Sie können unsafePerformIO verwenden, den Teufel von Haskell, der niemals verwendet werden sollte (und wahrscheinlich Ihr Programm brechen wird, wenn Sie Zufälligkeit verwenden drin!)
Alternative

In Haskell geben Sie entweder ein 'RandGen' weiter, das im Grunde den aktuellen Status des RNG darstellt. Die Funktion, die eine neue Zufallszahl generiert, nimmt ein RandGen und gibt ein Tupel mit dem neuen RandGen und der erzeugten Zahl zurück. Die Alternative besteht darin, irgendwo anzugeben, dass Sie eine Liste von Zufallszahlen zwischen einem Min- und einem Max-Wert wünschen. Dies gibt einen träge ausgewerteten unendlichen Strom von Zahlen zurück, sodass wir diese Liste einfach durchgehen können, wenn wir eine neue Zufallszahl benötigen.
Qqwy

Genauso wie du sie in jeder anderen Sprache bekommst! Sie erhalten einen Pseudozufallszahlengenerator-Algorithmus, setzen ihn dann mit einem bestimmten Wert und ernten die "zufälligen" Zahlen, die herausspringen! Der einzige Unterschied besteht darin, dass Sprachen wie C # und Java das PRNG automatisch mithilfe der Systemuhr oder ähnlichem für Sie festlegen. Dies und die Tatsache, dass Sie in haskell auch ein neues PRNG erhalten, mit dem Sie die "nächste" Nummer abrufen können, während dies in C # / Java intern mithilfe veränderbarer Variablen im RandomObjekt erfolgt.
Sara

4

Eine alte Frage, aber eine wirklich gute, also werde ich antworten.

Sie können sich Monaden als Codeblöcke vorstellen, für die Sie die vollständige Kontrolle darüber haben, wie sie ausgeführt werden: Was sollte jede Codezeile zurückgeben, ob die Ausführung an einem beliebigen Punkt gestoppt werden soll, ob zwischen jeder Zeile eine andere Verarbeitung stattfinden soll.

Ich werde einige Beispiele für Dinge geben, die Monaden ermöglichen, die sonst schwierig wären. Keines dieser Beispiele befindet sich in Haskell, nur weil mein Haskell-Wissen etwas wackelig ist, aber sie sind alle Beispiele dafür, wie Haskell die Verwendung von Monaden inspiriert hat.

Parser

Wenn Sie einen Parser schreiben möchten, beispielsweise um eine Programmiersprache zu implementieren, müssen Sie normalerweise entweder die BNF-Spezifikation lesen und eine ganze Reihe von Loop-Code schreiben, um ihn zu analysieren, oder Sie müssen einen Compiler-Compiler verwenden wie Flex, Bison, Yacc usw. Aber mit Monaden können Sie direkt in Haskell eine Art "Compiler-Parser" erstellen.

Parser können nicht wirklich ohne Monaden oder Spezialsprachen wie Yacc, Bison usw. durchgeführt werden.

Zum Beispiel habe ich die BNF-Sprachspezifikation für das IRC-Protokoll übernommen :

message    =  [ ":" prefix SPACE ] command [ params ] crlf
prefix     =  servername / ( nickname [ [ "!" user ] "@" host ] )
command    =  1*letter / 3digit
params     =  *14( SPACE middle ) [ SPACE ":" trailing ]
           =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]

nospcrlfcl =  %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
                ; any octet except NUL, CR, LF, " " and ":"
middle     =  nospcrlfcl *( ":" / nospcrlfcl )
trailing   =  *( ":" / " " / nospcrlfcl )

SPACE      =  %x20        ; space character
crlf       =  %x0D %x0A   ; "carriage return" "linefeed"

Und es in F # (einer anderen Sprache, die Monaden unterstützt) auf ungefähr 40 Codezeilen reduziert:

type UserIdentifier = { Name : string; User: string; Host: string }

type Message = { Prefix : UserIdentifier option; Command : string; Params : string list }

let space = character (char 0x20)

let parameters =
    let middle = parser {
        let! c = sat <| fun c -> c <> ':' && c <> (char 0x20)
        let! cs = many <| sat ((<>)(char 0x20))
        return (c::cs)
    }
    let trailing = many item
    let parameter = prefixed space ((prefixed (character ':') trailing) +++ middle)
    many parameter

let command = atLeastOne letter +++ (count 3 digit)

let prefix = parser {
    let! name = many <| sat (fun c -> c <> '!' && c <> '@' && c <> (char 0x20))   //this is more lenient than RFC2812 2.3.1
    let! uh = parser {
        let! user = maybe <| prefixed (character '!') (many <| sat (fun c -> c <> '@' && c <> (char 0x20)))
        let! host = maybe <| prefixed (character '@') (many <| sat ((<>) ' '))
        return (user, host)
    }
    let nullstr = function | Some([]) -> null | Some(s) -> charsString s | _ -> null
    return { Name = charsString name; User = nullstr (fst uh); Host = nullstr (snd uh) }
}

let message = parser {
    let! p = maybe (parser {
        let! _ = character ':'
        let! p = prefix
        let! _ = space
        return p
    })
    let! c = command
    let! ps = parameters
    return { Prefix = p; Command = charsString c; Params = List.map charsString ps }
}

Die Monadensyntax von F # ist im Vergleich zu der von Haskell ziemlich hässlich, und ich hätte dies wahrscheinlich ein wenig verbessern können - aber der Punkt, den man mit nach Hause nehmen sollte, ist, dass der Parser-Code strukturell mit dem BNF identisch ist. Dies hätte nicht nur viel mehr Arbeit ohne Monaden (oder einen Parser-Generator) gekostet, es hätte auch fast keine Ähnlichkeit mit der Spezifikation gehabt und wäre daher sowohl zu lesen als auch zu warten schrecklich gewesen.

Benutzerdefiniertes Multitasking

Normalerweise wird Multitasking als Betriebssystemfunktion betrachtet. Bei Monaden können Sie jedoch Ihren eigenen Scheduler so schreiben, dass das Programm nach jeder Anweisungsmonade die Kontrolle an den Scheduler übergibt, der dann eine andere auszuführende Monade auswählt.

Ein Typ machte eine "Task" -Monade , um Spielschleifen zu steuern (wieder in F #), so dass er, anstatt alles als Zustandsmaschine schreiben zu müssen, die auf jeden Update()Aufruf reagiert, einfach alle Anweisungen schreiben konnte, als wären sie eine einzelne Funktion .

Mit anderen Worten, anstatt etwas tun zu müssen wie:

class Robot
{
   enum State { Walking, Shooting, Stopped }

   State state = State.Stopped;

   public void Update()
   {
      switch(state)
      {
         case State.Stopped:
            Walk();
            state = State.Walking;
            break;
         case State.Walking:
            if (enemyInSight)
            {
               Shoot();
               state = State.Shooting;
            }
            break;
      }
   }
}

Sie könnten so etwas tun wie:

let robotActions = task {
   while (not enemyInSight) do
      Walk()
   while (enemyInSight) do
      Shoot()
}

LINQ zu SQL

LINQ to SQL ist eigentlich ein Beispiel für eine Monade, und ähnliche Funktionen könnten leicht in Haskell implementiert werden.

Ich werde nicht auf die Details eingehen, da ich mich nicht so genau erinnere, aber Erik Meijer erklärt es ziemlich gut .


1

Wenn Sie mit den GoF-Mustern vertraut sind, ähneln Monaden dem Decorator-Muster und dem Builder-Muster, die auf Steroiden zusammengesetzt sind und von einem radioaktiven Dachs gebissen werden.

Oben gibt es bessere Antworten, aber einige der spezifischen Vorteile, die ich sehe, sind:

  • Monaden schmücken einen Kerntyp mit zusätzlichen Eigenschaften, ohne den Kerntyp zu ändern. Zum Beispiel könnte eine Monade einen String "anheben" und Werte wie "isWellFormed", "isProfanity" oder "isPalindrome" usw. hinzufügen.

  • In ähnlicher Weise ermöglichen Monaden die Konglomeration eines einfachen Typs in einen Sammlungstyp

  • Monaden ermöglichen die späte Bindung von Funktionen in diesen Raum höherer Ordnung

  • Monaden ermöglichen das Mischen beliebiger Funktionen und Argumente mit einem beliebigen Datentyp im Raum höherer Ordnung

  • Monaden ermöglichen das Mischen von reinen, zustandslosen Funktionen mit einer unreinen, zustandsbehafteten Basis, sodass Sie verfolgen können, wo das Problem liegt

Ein bekanntes Beispiel für eine Monade in Java ist List. Es benötigt eine Kernklasse wie String und "hebt" sie in den Monadenbereich von List, um Informationen über die Liste hinzuzufügen. Dann bindet es neue Funktionen wie get (), getFirst (), add (), empty () usw. in diesen Raum.

Stellen Sie sich im großen Maßstab vor, Sie hätten statt eines Programms nur einen großen Builder (als GoF-Muster) geschrieben und die Methode build () am Ende die Antwort ausgespuckt, die das Programm erzeugen sollte. Und dass Sie Ihrem ProgramBuilder neue Methoden hinzufügen können, ohne den ursprünglichen Code neu zu kompilieren. Deshalb sind Monaden ein starkes Designmodell.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.