Monadi
Un monad consiste di
-
Un endofunctor . Nel nostro mondo dell'ingegneria del software, possiamo dire che questo corrisponde a un tipo di dati con un parametro di tipo unico, non limitato. In C #, questo sarebbe qualcosa di forma:
class M<T> { ... }
-
Due operazioni definite su quel tipo di dati:
-
return
/ pure
prende un valore "puro" (cioè, un valore T
) e "lo avvolge" nella monade (cioè produce un valore M<T>
). 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
prende un valore monadico ( M<A>
) e una funzione f
. f
prende 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, sono necessari pure
e bind
per obbedire alle tre leggi monad.
Ora, un modo per modellare le monadi in C # sarebbe quello di costruire un'interfaccia:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Nota: per mantenere le cose brevi ed espressive, mi prenderò delle libertà con il codice in tutta questa risposta.)
Ora possiamo implementare le monadi per i datagypes 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);
}
}
(Uso 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 sì:
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 conosco abbastanza bene con Rx.NET per sapere se possono essere provati solo dalle specifiche), ma è un inizio promettente. Per facilitare il resto di questa discussione, supponiamo che le leggi della monade siano valide in questo caso.
Monade gratuite
Non esiste una singolare "monade libera". Piuttosto, le monadi libere sono una classe di monadi costruite dai funtori. Cioè, dato un functor F
, possiamo automaticamente derivare una monade per F
(cioè, la monade libera di F
).
Funtori
Come le monadi, i funtori possono essere definiti dai seguenti tre elementi:
- Un tipo di dati, parametrizzato su una singola variabile di tipo non ristretto.
-
Due operazioni:
-
pure
avvolge un valore puro nel functor. Questo è analogo a pure
per una monade. Infatti, per i funtori che sono anche una monade, i due dovrebbero essere identici.
-
fmap
mappa 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, ai funtori è richiesto di obbedire alle leggi del funtore.
Simile alle monadi, possiamo modellare i funtori tramite la seguente interfaccia:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Ora, dal momento che le monadi sono una sottoclasse di funtori, potremmo anche refactificare 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 join
e bind
. Si noti, tuttavia, che queste sono definizioni circolari. Quindi dovresti scavalcare almeno l'uno o l'altro. Inoltre, nota che pure
è ora ereditato da Functor
.
IObservable
e Monade gratuite
Ora, poiché abbiamo definito una monade per IObservable
e poiché le monadi sono una sottoclasse di funtori, ne consegue che dobbiamo essere in grado di definire un'istanza di functor 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 un functor definito per IObservable
, possiamo costruire una monade libera da quel functor. E questo è esattamente il modo in cui IObservable
si riferisce alle monadi libere, vale a dire che possiamo costruire una monade libera da IObservable
.