Monades
Une monade se compose de
Un endofuncteur . Dans notre monde du génie logiciel, nous pouvons dire que cela correspond à un type de données avec un seul paramètre de type sans restriction. En C #, ce serait quelque chose de la forme:
class M<T> { ... }
Deux opérations définies sur ce type de données:
return
/ pure
prend une valeur "pure" (c'est-à-dire une T
valeur) et "l'enveloppe" dans la monade (c'est-à-dire qu'elle produit une M<T>
valeur). Puisque return
c'est un mot-clé réservé en C #, je vais utiliser pure
pour faire référence à cette opération à partir de maintenant. En C #, pure
serait une méthode avec une signature comme:
M<T> pure(T v);
bind
/ flatmap
prend une valeur monadique ( M<A>
) et une fonction f
. f
prend une valeur pure et retourne une valeur monadique ( M<B>
). À partir de ceux-ci, bind
produit une nouvelle valeur monadique ( M<B>
). bind
a la signature C # suivante:
M<B> bind(M<A> mv, Func<A, M<B>> f);
En outre, pour être une monade, pure
et bind
sont tenus d'obéir aux trois lois de la monade.
Maintenant, une façon de modéliser des monades en C # serait de construire une interface:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Remarque: afin de garder les choses brèves et expressives, je vais prendre quelques libertés avec le code tout au long de cette réponse.)
Nous pouvons maintenant implémenter des monades pour des types de données concrets en implémentant des implémentations concrètes de Monad<M>
. Par exemple, nous pouvons implémenter la monade suivante pour 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);
}
}
(J'utilise délibérément la syntaxe LINQ pour appeler la relation entre la syntaxe LINQ et les monades. Mais notez que nous pourrions remplacer la requête LINQ par un appel à SelectMany
.)
Maintenant, pouvons-nous définir une monade pour IObservable
? Il semblerait donc:
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);
}
}
Pour être sûr que nous avons une monade, nous devons prouver les lois de la monade. Cela peut être non trivial (et je ne connais pas suffisamment Rx.NET pour savoir s'ils peuvent même être prouvés à partir de la seule spécification), mais c'est un début prometteur. Pour faciliter le reste de cette discussion, supposons simplement que les lois de la monade s'appliquent dans ce cas.
Monades gratuites
Il n'y a pas de "monade libre" singulière. Les monades libres sont plutôt une classe de monades construites à partir de foncteurs. Autrement dit, étant donné un foncteur F
, nous pouvons automatiquement dériver une monade pour F
(c'est-à-dire la monade libre de F
).
Foncteurs
Comme les monades, les foncteurs peuvent être définis par les trois éléments suivants:
- Un type de données, paramétré sur une seule variable de type sans restriction.
Deux opérations:
pure
encapsule une valeur pure dans le foncteur. Ceci est analogue à pure
une monade. En fait, pour les foncteurs qui sont aussi des monades, les deux doivent être identiques.
fmap
mappe les valeurs de l'entrée aux nouvelles valeurs de la sortie via une fonction donnée. Sa signature est:
F<B> fmap(Func<A, B> f, F<A> fv)
Comme les monades, les foncteurs sont tenus d'obéir aux lois des foncteurs.
Comme pour les monades, nous pouvons modéliser des foncteurs via l'interface suivante:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Maintenant, comme les monades sont une sous-classe de foncteurs, nous pourrions également refactoriser Monad
un peu:
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));
}
}
Ici, j'ai ajouté une méthode supplémentaire join
, et fourni des implémentations par défaut de join
et bind
. Notez cependant qu'il s'agit de définitions circulaires. Vous devez donc remplacer au moins l'un ou l'autre. Notez également qu'il pure
est désormais hérité de Functor
.
IObservable
et monades gratuites
Maintenant, puisque nous avons défini une monade pour IObservable
et puisque les monades sont une sous-classe de foncteurs, il s'ensuit que nous devons être en mesure de définir une instance de foncteur pour IObservable
. Voici une définition:
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);
}
}
Maintenant que nous avons défini un foncteur IObservable
, nous pouvons construire une monade libre à partir de ce foncteur. Et c'est précisément ce qui se IObservable
rapporte aux monades libres - à savoir, que nous pouvons construire une monade libre à partir de IObservable
.
Cont
c'est la seule monade que j'ai vue suggérer qui ne peut pas être exprimée via la monade libre, on peut probablement supposer que FRP peut l'être. Comme presque tout le reste .