Hallo an alle.
Nach vier Jahren Programmierung auf Scala ist mein Verständnis von Monaden endlich so weit gewachsen, dass man es anderen erklären kann, ohne auf die Kategorietheorie und die klassische
Monade Bezug zu nehmen
- es ist nur ein Monoid in der Kategorie der Endofunder , das Programmierer nicht schlechter macht als Dichlorvos-Kakerlaken.
Codebeispiele werden in Kotlin, as geschrieben es ist sehr beliebt und gleichzeitig sehr funktional (in beiden Sinne des Wortes).
Beginnen wir mit dem Konzept eines
Funktors , hier ist es:
interface Functor<A>
Was bedeutet es? Ein Funktor ist eine Abstraktion einer beliebigen Berechnung, die ein Ergebnis vom Typ A zurückgibt. Wir abstrahieren davon, wie ein neuer Funktor erstellt wird und vor allem, wie sein Wert A berechnet wird. Insbesondere kann sich eine Funktion hinter einer Funktorschnittstelle verstecken mit einer beliebigen Anzahl von Argumenten und nicht unbedingt einer reinen Funktion.
Beispiele für Funktorimplementierungen:
- konstant
- Funktion mit einer beliebigen Anzahl von Argumenten, die ein Ergebnis vom Typ
A
zurückgibt - Zustand Pseudozufallsgenerator (Random)
- Hardware-Zufallszahlengenerator
- Lesen eines Objekts von der Festplatte oder vom Netzwerk
- asynchrone Berechnung - Ein Rückruf wird an die Funktorimplementierung übergeben, die später aufgerufen wird
Alle diese Beispiele mit Ausnahme der Konstanten haben eine wichtige Eigenschaft - sie sind faul, d.h. Die Berechnung selbst erfolgt nicht beim Erstellen des Funktors, sondern beim Berechnen.
Die Funktorschnittstelle ermöglicht weder das Abrufen eines Werts vom Typ
A
von Funktor
Functor<A>
noch das Erstellen eines neuen
Functor<A>
aus einem vorhandenen Wert vom Typ
A
Aber selbst mit solchen Einschränkungen ist der Funktor nicht nutzlos - wenn wir für einen Typ
B
A
in
B
konvertieren können (mit anderen Worten, es gibt eine Funktion
(a: A) -> B
), dann können wir eine Funktion schreiben
(f: Functor<A>) -> Functor<B>
und nennen Sie es
map
:
interface Functor<A> { fun <B> map(f: (A) -> B): Functor<B> }
Im Gegensatz zum Funktor selbst kann die Kartenmethode keine beliebige Funktion sein:
-
map((a) -> a)
sollte denselben Funktor zurückgeben
-
map((a) -> f(a)).map((b) -> g(b))
muss mit
map(a -> g(f(a))
identisch sein.
Als Beispiel implementieren wir einen Funktor, der einen A-Wert zurückgibt, der eine bestimmte Anzahl von Zufallsbits enthält. Unsere Schnittstelle in Kotlin kann nicht so einfach verwendet werden (aber Sie
können es , falls gewünscht), daher werden wir eine Erweiterungsmethode schreiben:
Andere Beispiele für Funktoren mit einer nützlichen
map
- unveränderliche
List<A>
MyInputStream<A>
Optional<A>
Jetzt können Sie zu Monaden gehen.
Eine Monade ist ein Funktor mit zwei zusätzlichen Operationen. Erstens enthält die Monade im Gegensatz zum Funktor die Operation des Erzeugens aus einer Konstanten. Diese Operation wird als
lift
:
fun <A> lift(value: A): Monad<A> = TODO()
Die zweite Operation heißt
flatMap
und ist komplizierter. Zuerst geben wir unsere gesamte Monadenschnittstelle an:
interface Monad<A> {
Der wichtigste Unterschied zwischen einer Monade und einem Funktor besteht darin, dass Monaden miteinander
kombiniert werden können, um neue Monaden zu erzeugen und von der Implementierung der Monade zu abstrahieren - ob sie von der Festplatte liest, ob sie zusätzliche Parameter zur Berechnung ihres Werts akzeptiert, existiert dieser Wert . Der zweite wichtige Punkt - Monaden werden nicht parallel, sondern nacheinander kombiniert, sodass je nach Ergebnis der ersten Monade Logik hinzugefügt werden kann.
Ein Beispiel:
In diesem Beispiel wird jedoch kein Netzwerk erwähnt. Ebenso gut können Daten aus einer Datei oder aus einer Datenbank gelesen werden. Sie können synchron oder asynchron gelesen werden, hier kann es zu einer Fehlerbehandlung kommen - alles hängt von der spezifischen Implementierung der Monade ab, der Code selbst bleibt unverändert.
Das Beispiel ist zunächst einfacher, Option Monade. In Kotlin wird es nicht wirklich benötigt, aber in Java / Scala ist es äußerst nützlich:
data class Option<A>(val value: A?) { fun <B> map(f: (A) -> B): Option<B> = flatMap { a -> lift(f(a)) } fun <B> flatMap(f: (A) -> Option<B>): Option<B> = when(value) { null -> Option(null) else -> f(value) } } fun <A> lift(value: A?): Option<A> = Option(value) fun mul(a: Option<Int>, b: Option<Int>): Option<Int> = a.flatMap { a -> b.map { b -> a * b } } fun main(args: Array<String>) { println(mul(Option(4), Option(5)).value)
Lassen Sie uns als Monade der Pozakovyristie die Arbeit mit der Datenbank in der Monade abschließen:
data class DB<A>(val f: (Connection) -> A) { fun <B> map(f: (A) -> B): DB<B> = flatMap { a -> lift(f(a)) } fun <B> flatMap(f: (A) -> DB<B>): DB<B> = DB { conn -> f(this.f(conn)).f(conn) } } fun <A> lift(value: A): DB<A> = DB { value } fun select(id: Int): DB<String> = DB { conn -> val st = conn.createStatement()
Ist das Kaninchenloch tief?
Es gibt eine Vielzahl von Monaden, aber ihr Hauptzweck besteht darin, die Geschäftslogik der Anwendung von einigen Details der durchgeführten Berechnungen zu abstrahieren:
- dass der Wert möglicherweise nicht vorhanden ist:
data class Option<A>(value: A?)
- dass die Berechnung fehlschlägt:
data class Either<Error, A>(value: Pair<Error?, A?>)
- dass die Berechnung faul sein kann:
data class Defer<A>(value: () -> A)
- oder asynchron:
java.util.concurrent.CompletableFuture<A>
- oder einen Funktionszustand haben:
data class State<S, A>(value: (S) -> Pair<S, A>)
Liste der unbeantworteten Fragen:
- Applikative Funktoren - eine Zwischenverbindung zwischen Funktoren und Monaden
- Sammlungen wie Monaden
- Kompositionen monotypischer Monaden - Pfeilkleber, monadische Transformatoren
- Sequenz / Traverse
- Monaden als Effekte
- Monaden und Rekursion, Stapelüberlauf, Trampolin
- Tagless endgültige Codierung
- Io Monade
- und im Allgemeinen der ganze Zoo der Standardmonaden
Was weiter?
Pfeil-kt.iotypelevel.org/cats/typeclasses.htmlwiki.haskell.org/All_About_MonadsMein Experiment ist eine vollwertige FP-Anwendung auf Scala:
github.com/scf37/fpscala2PS Ich wollte eine kleine Notiz, es stellte sich wie immer heraus.