Monaden
Eine Monade besteht aus
Ein Endofunktor . In unserer Welt der Softwareentwicklung können wir sagen, dass dies einem Datentyp mit einem einzigen, uneingeschränkten Typparameter entspricht. In C # wäre dies etwa so:
class M<T> { ... }
Zwei über diesen Datentyp definierte Operationen:
return
Ich pure
nehme einen "reinen" Wert (dh einen T
Wert) und "hülle" ihn in die Monade (dh es wird ein M<T>
Wert erzeugt ). Da return
es sich bei C # um ein reserviertes Schlüsselwort handelt, werde ich pure
von nun an auf diesen Vorgang verweisen. In C # pure
wäre eine Methode mit einer Signatur wie:
M<T> pure(T v);
bind
/ flatmap
nimmt einen monadischen Wert ( M<A>
) und eine Funktion an f
. f
Nimmt einen reinen Wert und gibt einen monadischen Wert zurück ( M<B>
). Daraus bind
ergibt einen neuen monadischen Wert ( M<B>
). bind
hat die folgende C # Signatur:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Auch, um eine Monade zu sein, pure
und bind
müssen die drei Monadengesetze befolgen.
Eine Möglichkeit, Monaden in C # zu modellieren, besteht darin, eine Schnittstelle zu erstellen:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Hinweis: Um die Dinge kurz und aussagekräftig zu halten, nehme ich mir in dieser Antwort einige Freiheiten mit dem Code.)
Jetzt können wir Monaden für konkrete Datentypen implementieren, indem wir konkrete Implementierungen von implementieren Monad<M>
. Beispielsweise könnten wir die folgende Monade implementieren für IEnumerable
:
class IEnumerableM implements Monad<IEnumerable> {
IEnumerable<T> pure(T v) {
return (new List<T>(){v}).AsReadOnly();
}
IEnumerable<B> bind(IEnumerable<A> mv, Func<A, IEnumerable<B>> f) {
;; equivalent to mv.SelectMany(f)
return (from a in mv
from b in f(a)
select b);
}
}
(Ich verwende die LINQ-Syntax absichtlich, um die Beziehung zwischen LINQ-Syntax und Monaden herauszufinden. Beachten Sie jedoch, dass wir die LINQ-Abfrage durch einen Aufruf von ersetzen können SelectMany
.)
Können wir nun eine Monade definieren für IObservable
? Es scheint so:
class IObservableM implements Monad<IObservable> {
IObservable<T> pure(T v){
Observable.Return(v);
}
IObservable<B> bind(IObservable<A> mv, Func<A, IObservable<B>> f){
mv.SelectMany(f);
}
}
Um sicherzugehen, dass wir eine Monade haben, müssen wir die Monadengesetze beweisen. Dies kann nicht trivial sein (und ich kenne Rx.NET nicht gut genug, um zu wissen, ob sie auch nur anhand der Spezifikation bewiesen werden können), aber es ist ein vielversprechender Anfang. Um den Rest dieser Diskussion zu vereinfachen, nehmen wir einfach die in diesem Fall geltenden Monadengesetze an.
Freie Monaden
Es gibt keine singuläre "freie Monade". Freie Monaden sind vielmehr eine Klasse von Monaden, die aus Funktoren aufgebaut sind. Das heißt, wenn ein Funktor gegeben ist F
, können wir automatisch eine Monade für F
(dh die freie Monade von F
) ableiten .
Functors
Wie Monaden können Funktoren durch die folgenden drei Elemente definiert werden:
- Ein Datentyp, der über eine einzelne Variable vom Typ ohne Einschränkungen parametrisiert wird.
Zwei Operationen:
pure
Wickelt einen reinen Wert in den Funktor. Dies ist analog zu pure
einer Monade. Tatsächlich sollten für Funktoren, die auch Monaden sind, die beiden identisch sein.
fmap
Ordnet Werte in der Eingabe über eine bestimmte Funktion neuen Werten in der Ausgabe zu. Ihre Unterschrift lautet:
F<B> fmap(Func<A, B> f, F<A> fv)
Wie Monaden müssen auch Funktoren die Funktorgesetze einhalten.
Ähnlich wie bei Monaden können wir Funktoren über die folgende Schnittstelle modellieren:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Nun, da Monaden eine Unterklasse von Funktoren sind, können wir auch Monad
ein wenig überarbeiten :
interface Monad<M> extends Functor<M> {
M<T> join(M<M<T>> mmv) {
Func<T, T> identity = (x => x);
return mmv.bind(x => x); // identity function
}
M<B> bind(M<A> mv, Func<A, M<B>> f) {
join(fmap(f, mv));
}
}
Hier habe ich eine zusätzliche Methode hinzugefügt join
und Standardimplementierungen von beiden join
und bereitgestellt bind
. Beachten Sie jedoch, dass dies zirkuläre Definitionen sind. Sie müssten also mindestens das eine oder andere außer Kraft setzen. Beachten Sie außerdem, dass pure
jetzt von geerbt wird Functor
.
IObservable
und freie Monaden
Da wir nun eine Monade für definiert haben IObservable
und Monaden eine Unterklasse von Funktoren sind, müssen wir in der Lage sein, eine Funktorinstanz für zu definieren IObservable
. Hier ist eine Definition:
class IObservableF implements Functor<IObservable> {
IObservable<T> pure(T v) {
return Observable.Return(v);
}
IObservable<B> fmap(Func<A, B> f, IObservable<A> fv){
return fv.Select(f);
}
}
Jetzt, da wir einen Funktor definiert haben IObservable
, können wir aus diesem Funktor eine freie Monade konstruieren. Und genau so IObservable
verhält es sich mit freien Monaden - nämlich, aus denen wir eine freie Monade konstruieren können IObservable
.
Cont
die einzige Monade ist, die ich gesehen habe und die nicht über die freie Monade ausgedrückt werden kann, kann man wahrscheinlich davon ausgehen, dass es sich um eine FRP handelt. Da kann fast alles andere .