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/ pureprende un valore "puro" (cioè un Tvalore) e lo "avvolge" nella monade (cioè produce un M<T>valore). Poiché returnè una parola chiave riservata in C #, userò pureper fare riferimento a questa operazione da ora in poi. In C #, puresarebbe un metodo con una firma come:
M<T> pure(T v);
bind/ flatmapaccetta un valore monadico ( M<A>) e una funzione f. faccetta un valore puro e restituisce un valore monadico ( M<B>). Da questi, bindproduce un nuovo valore monadico ( M<B>). bindha la seguente firma C #:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Inoltre, per essere una monade, puree bindsono 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:
pureavvolge un valore puro nel functor. Questo è analogo a pureper una monade. In effetti, per i funzioni che sono anche monadi, i due dovrebbero essere identici.
fmapassocia 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 Monadun 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 joine fornito implementazioni predefinite di entrambi joine bind. Si noti, tuttavia, che si tratta di definizioni circolari. Quindi dovresti scavalcare almeno l'uno o l'altro. Inoltre, si noti che pureora è ereditato da Functor.
IObservable e Monadi libere
Ora, poiché abbiamo definito una monade per IObservablee 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 IObservableriguarda 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 .