Mônadas
Uma mônada consiste em
Um endofuncor . Em nosso mundo de engenharia de software, podemos dizer que isso corresponde a um tipo de dados com um único parâmetro de tipo irrestrito. Em C #, isso seria algo da forma:
class M<T> { ... }
Duas operações definidas sobre esse tipo de dados:
return
/ pure
pega um valor "puro" (ou seja, um T
valor) e o "envolve" na mônada (ou seja, produz um M<T>
valor). Como return
é uma palavra-chave reservada em C #, usarei pure
para me referir a essa operação a partir de agora. Em C #, pure
seria um método com uma assinatura como:
M<T> pure(T v);
bind
/ flatmap
pega um valor monádico ( M<A>
) e uma função f
. f
pega um valor puro e retorna um valor monádico ( M<B>
). A partir disso, bind
produz um novo valor monádico ( M<B>
). bind
tem a seguinte assinatura C #:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Além disso, para ser uma mônada, pure
e bind
são obrigados a obedecer às três leis da mônada.
Agora, uma maneira de modelar mônadas em C # seria construir uma interface:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Observação: para manter as coisas breves e expressivas, usarei algumas liberdades com o código ao longo desta resposta.)
Agora podemos implementar mônadas para tipos de dados concretos implementando implementações concretas de Monad<M>
. Por exemplo, podemos implementar a seguinte mônada para 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);
}
}
(Estou usando intencionalmente a sintaxe LINQ para chamar o relacionamento entre a sintaxe LINQ e as mônadas. Mas observe que poderíamos substituir a consulta LINQ por uma chamada para SelectMany
.)
Agora, podemos definir uma mônada para IObservable
? Parece que sim:
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);
}
}
Para ter certeza de que temos uma mônada, precisamos provar as leis da mônada. Isso pode não ser trivial (e eu não estou familiarizado o suficiente com o Rx.NET para saber se eles podem ser provados apenas a partir da especificação), mas é um começo promissor. Para facilitar o restante desta discussão, vamos apenas assumir que as leis de mônada são válidas nesse caso.
Mônadas grátis
Não existe uma "mônada livre" singular. Em vez disso, mônadas livres são uma classe de mônadas que são construídas a partir de functores. Ou seja, dado um functor F
, podemos derivar automaticamente uma mônada para F
(isto é, a mônada livre de F
).
Functors
Como mônadas, os functores podem ser definidos pelos três itens a seguir:
- Um tipo de dados, parametrizado em uma única variável de tipo irrestrita.
Duas operações:
pure
agrupa um valor puro no functor. Isso é análogo a pure
uma mônada. De fato, para os functores que também são mônadas, os dois devem ser idênticos.
fmap
mapeia valores na entrada para novos valores na saída através de uma determinada função. Sua assinatura é:
F<B> fmap(Func<A, B> f, F<A> fv)
Como mônadas, os functores são obrigados a obedecer às leis dos functores.
Semelhante às mônadas, podemos modelar functors através da seguinte interface:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Agora, como as mônadas são uma subclasse de functores, também podemos refatorar Monad
um pouco:
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));
}
}
Aqui eu adicionei um método adicional,, join
e forneci implementações padrão de ambos join
e bind
. Observe, no entanto, que essas são definições circulares. Então você teria que substituir pelo menos um ou outro. Além disso, observe que pure
agora é herdado de Functor
.
IObservable
e Mônadas Grátis
Agora, como definimos uma mônada para IObservable
e como as mônadas são uma subclasse de functores, segue-se que devemos ser capazes de definir uma instância de functor para IObservable
. Aqui está uma definição:
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);
}
}
Agora que temos um functor definido IObservable
, podemos construir uma mônada livre a partir desse functor. E é exatamente assim que IObservable
se relaciona com as mônadas livres - a partir da qual podemos construir uma mônada livre IObservable
.
Cont
é a única mônada que vi sugerida que não pode ser expressa através da mônada livre, é provável que se possa supor que o FRP possa ser. Como pode quase qualquer outra coisa .