Monady
Monada składa się z
Endofunctor . W naszym świecie inżynierii oprogramowania możemy powiedzieć, że odpowiada to typowi danych z pojedynczym, nieograniczonym parametrem typu. W języku C # byłoby to coś w rodzaju:
class M<T> { ... }
Dwie operacje zdefiniowane dla tego typu danych:
return
/ pure
przyjmuje „czystą” wartość (tj. T
wartość) i „zawija” ją w monadę (tzn. tworzy M<T>
wartość). Ponieważ return
jest to zastrzeżone słowo kluczowe w języku C #, pure
odtąd będę się odnosił do tej operacji. W języku C # pure
byłaby metoda z podpisem:
M<T> pure(T v);
bind
/ flatmap
przyjmuje wartość monadyczną ( M<A>
) i funkcję f
. f
przyjmuje wartość czystą i zwraca wartość monadyczną ( M<B>
). Z nich bind
tworzy nową wartość monadyczną ( M<B>
). bind
ma następujący podpis w języku C #:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Ponadto, aby być monadą pure
i bind
muszą przestrzegać trzech praw monady.
Teraz jednym ze sposobów modelowania monad w języku C # byłoby zbudowanie interfejsu:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Uwaga: aby wszystko było krótkie i wyraziste, w ramach tej odpowiedzi skorzystam z pewnej wolności z kodem).
Teraz możemy zaimplementować monady dla konkretnych typów danych, wdrażając konkretne implementacje Monad<M>
. Na przykład możemy zaimplementować następującą monadę dla 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);
}
}
(Celowo używam składni LINQ do wywołania związku między składnią LINQ a monadami. Pamiętaj jednak, że możemy zastąpić zapytanie LINQ wywołaniem do SelectMany
).
Czy możemy teraz zdefiniować monadę IObservable
? Wydawałoby się tak:
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);
}
}
Aby mieć pewność, że mamy monadę, musimy udowodnić jej prawa. Może to być trywialne (i nie znam się na Rx.NET, aby wiedzieć, czy można je udowodnić na podstawie samej specyfikacji), ale jest to obiecujący początek. Aby ułatwić pozostałą część tej dyskusji, załóżmy, że obowiązują w tym przypadku prawa monad.
Darmowe Monady
Nie ma pojedynczej „wolnej monady”. Wolne monady są raczej klasą monad zbudowanych z funktorów. To znaczy, biorąc pod uwagę funktor F
, możemy automatycznie wyprowadzić monadę F
(tj. Wolną monadę F
).
Functors
Podobnie jak monady, funktory można zdefiniować za pomocą następujących trzech elementów:
- Typ danych sparametryzowany za pomocą jednej, nieograniczonej zmiennej typu.
Dwie operacje:
pure
otacza czystą wartość funktorem. Jest to analogiczne do pure
monady. W rzeczywistości dla funktorów, które są również monadami, oba powinny być identyczne.
fmap
odwzorowuje wartości na wejściu na nowe wartości na wyjściu za pomocą danej funkcji. Jego podpis to:
F<B> fmap(Func<A, B> f, F<A> fv)
Podobnie jak monady, funktory są zobowiązane do przestrzegania praw funktorów.
Podobnie jak monady, możemy modelować funktory za pomocą następującego interfejsu:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Teraz, ponieważ monady są podklasą funktorów, moglibyśmy również Monad
trochę zmienić :
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));
}
}
Tutaj dodałem dodatkową metodę join
i zapewniłem domyślne implementacje zarówno join
i bind
. Należy jednak pamiętać, że są to definicje kołowe. Więc musisz zastąpić co najmniej jedno lub drugie. Zauważ też, że pure
teraz jest dziedziczony Functor
.
IObservable
i darmowe monady
Ponieważ zdefiniowaliśmy monadę dla, IObservable
a ponieważ monady są podklasą funktorów, z tego wynika, że musimy być w stanie zdefiniować instancję funktora IObservable
. Oto jedna definicja:
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);
}
}
Teraz, gdy mamy zdefiniowany funktor IObservable
, możemy z niego zbudować wolną monadę. I właśnie tak IObservable
odnosi się do wolnych monad - mianowicie, że możemy zbudować wolną monadę IObservable
.
Cont
jest to jedyna monada, jaką widziałem, której nie można wyrazić za pomocą wolnej monady, prawdopodobnie można założyć, że FRP może być. Jak prawie wszystko inne .