Ich verstehe einfach nicht, welches Problem sie lösen.
Ich verstehe einfach nicht, welches Problem sie lösen.
Antworten:
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 putStrLn
die 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 putStrLn
gibt 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).
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 goto
Rückkehr. Es ist einfach so, dass man nicht argumentieren kann, dass goto
dies für Leute, die es ausgiebig nutzen , nicht notwendig ist.
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.
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.
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.
Random
Objekt erfolgt.
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 .
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.