monadi
Una monade è composta da
Un endofunctor . Nel nostro mondo dell'ingegneria del software, possiamo dire che questo corrisponde a un tipo di dati con un singolo parametro di tipo senza restrizioni. In C #, questo sarebbe qualcosa della forma:
class M<T> { ... }
Due operazioni definite su quel tipo di dati:
return
/ pure
prende un valore "puro" (cioè un T
valore) e lo "avvolge" nella monade (cioè produce un M<T>
valore). Poiché return
è una parola chiave riservata in C #, userò pure
per fare riferimento a questa operazione da ora in poi. In C #, pure
sarebbe un metodo con una firma come:
M<T> pure(T v);
bind
/ flatmap
accetta un valore monadico ( M<A>
) e una funzione f
. f
accetta un valore puro e restituisce un valore monadico ( M<B>
). Da questi, bind
produce un nuovo valore monadico ( M<B>
). bind
ha la seguente firma C #:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Inoltre, per essere una monade, pure
e bind
sono tenuti ad obbedire alle tre leggi della monade.
Ora, un modo per modellare le monadi in C # sarebbe costruire un'interfaccia:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Nota: al fine di mantenere le cose brevi ed espressive, mi prenderò alcune libertà con il codice in tutta questa risposta.)
Ora possiamo implementare monadi per tipi di dati concreti implementando implementazioni concrete di Monad<M>
. Ad esempio, potremmo implementare la seguente monade per 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);
}
}
(Sto usando intenzionalmente la sintassi LINQ per richiamare la relazione tra sintassi LINQ e monadi. Ma nota che potremmo sostituire la query LINQ con una chiamata a SelectMany
.)
Ora, possiamo definire una monade per IObservable
? Sembrerebbe di si:
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);
}
}
Per essere sicuri di avere una monade, dobbiamo dimostrare le leggi della monade. Questo può essere non banale (e non ho abbastanza familiarità con Rx.NET per sapere se possono essere provati anche solo dalle specifiche), ma è un inizio promettente. Per facilitare il resto di questa discussione, supponiamo che le leggi sulla monade siano valide in questo caso.
Monadi libere
Non esiste una "monade libera" singolare. Piuttosto, le monadi libere sono una classe di monadi costruite da funzioni. Cioè, dato un funzione F
, possiamo automaticamente derivare una monade per F
(cioè la monade libera di F
).
funtori
Come le monadi, i funzione possono essere definiti dai seguenti tre elementi:
- Un tipo di dati, parametrizzato su una singola variabile di tipo senza restrizioni.
Due operazioni:
pure
avvolge un valore puro nel functor. Questo è analogo a pure
per una monade. In effetti, per i funzioni che sono anche monadi, i due dovrebbero essere identici.
fmap
associa i valori nell'input a nuovi valori nell'output tramite una determinata funzione. La sua firma è:
F<B> fmap(Func<A, B> f, F<A> fv)
Come le monadi, i funzioni sono tenuti a obbedire alle leggi dei funzioni.
Simile alle monadi, possiamo modellare i funzione tramite la seguente interfaccia:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Ora, poiché le monadi sono una sottoclasse di funzioni, potremmo anche rifattorizzare Monad
un po ':
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));
}
}
Qui ho aggiunto un metodo aggiuntivo join
e fornito implementazioni predefinite di entrambi join
e bind
. Si noti, tuttavia, che si tratta di definizioni circolari. Quindi dovresti scavalcare almeno l'uno o l'altro. Inoltre, si noti che pure
ora è ereditato da Functor
.
IObservable
e Monadi libere
Ora, poiché abbiamo definito una monade per IObservable
e poiché le monadi sono una sottoclasse di funzioni, ne consegue che dobbiamo essere in grado di definire un'istanza di funzione per IObservable
. Ecco una definizione:
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);
}
}
Ora che abbiamo definito un funzione IObservable
, possiamo costruire una monade libera da quel funzione. Ed è proprio così che IObservable
riguarda le monadi libere - vale a dire, da cui possiamo costruire una monade libera IObservable
.
Cont
è l'unica monade che ho visto suggerire che non può essere espressa tramite la monade libera, si può presumere che FRP possa esserlo. Come quasi ogni altra cosa .