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/ pureprzyjmuje „czystą” wartość (tj. Twartość) i „zawija” ją w monadę (tzn. tworzy M<T>wartość). Ponieważ returnjest to zastrzeżone słowo kluczowe w języku C #, pureodtąd będę się odnosił do tej operacji. W języku C # purebyłaby metoda z podpisem:
M<T> pure(T v);
bind/ flatmapprzyjmuje wartość monadyczną ( M<A>) i funkcję f. fprzyjmuje wartość czystą i zwraca wartość monadyczną ( M<B>). Z nich bindtworzy nową wartość monadyczną ( M<B>). bindma następujący podpis w języku C #:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Ponadto, aby być monadą purei bindmuszą 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:
pureotacza czystą wartość funktorem. Jest to analogiczne do puremonady. W rzeczywistości dla funktorów, które są również monadami, oba powinny być identyczne.
fmapodwzorowuje 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ż Monadtrochę 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ę joini zapewniłem domyślne implementacje zarówno joini 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 pureteraz jest dziedziczony Functor.
IObservable i darmowe monady
Ponieważ zdefiniowaliśmy monadę dla, IObservablea 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 IObservableodnosi się do wolnych monad - mianowicie, że możemy zbudować wolną monadę IObservable.
Contjest 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 .