Cela fait un an que j'ai posté cette question. Après l'avoir publiée, je me suis plongé dans Haskell pendant quelques mois. J'ai énormément apprécié, mais je l'ai mis de côté au moment où j'étais prêt à me plonger dans les Monads. Je suis retourné au travail et me suis concentré sur les technologies requises par mon projet.
C'est plutôt cool. C'est un peu abstrait cependant. Je peux imaginer des gens qui ne savent pas ce que sont les monades soient déjà confus à cause du manque de d'exemples réels.
J'essaierai donc de m'y conformer, et pour être vraiment clair, je ferai un exemple en C#, même s'il sera laid. exemple en C#, même s'il sera laid. J'ajouterai l'équivalent Haskell à la fin et je vous montrerai le sucre syntaxique cool de Haskell. c'est là que, IMO, les monades commencent vraiment à être utiles.
Ok, donc l'un des monades les plus simples est appelé le "Maybe monad" en Haskell. En C#, le type Maybe est appelé Nullable<T>
. Il s'agit essentiellement une petite classe qui encapsule simplement le concept d'une valeur qui est soit valide et a une valeur, soit "nulle" et n'a pas de valeur.
Un élément utile à placer à l'intérieur d'une monade pour combiner des valeurs de ce type est le suivant type est la notion d'échec. C'est à dire que nous voulons être capable d'examiner plusieurs valeurs nullables et retourner null
dès que l'un d'entre eux est nulle. Cela peut être utile si, par exemple, vous recherchez beaucoup de clés beaucoup de clés dans un dictionnaire ou autre, et qu'à la fin vous voulez traiter tous les résultats et les combiner d'une manière ou d'une autre, mais si l'une des clés n'est pas dans le dictionnaire, vous voulez renvoyer null
pour l'ensemble truc. Il serait fastidieux de devoir vérifier manuellement pour chaque recherche la présence de null
et de retour, nous pouvons donc cacher cette vérification dans l'opérateur bind (ce qui est en quelque sorte l'intérêt des monades, nous cachons la comptabilité dans l'opérateur bind ce qui rend le code plus facile à utiliser puisque nous pouvons oublier les détails).
Voici le programme qui motive l'ensemble (je définirai les Bind
plus tard, c'est juste pour vous montrer pourquoi c'est bien).
class Program
{
static Nullable<int> f(){ return 4; }
static Nullable<int> g(){ return 7; }
static Nullable<int> h(){ return 9; }
static void Main(string[] args)
{
Nullable<int> z =
f().Bind( fval =>
g().Bind( gval =>
h().Bind( hval =>
new Nullable<int>( fval + gval + hval ))));
Console.WriteLine(
"z = {0}", z.HasValue ? z.Value.ToString() : "null" );
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
Maintenant, ignorez pour un moment qu'il y a déjà un support pour faire ceci pour Nullable
en C# (vous pouvez ajouter des ints nullables ensemble et vous obtenez null si l'un des deux est nul). Imaginons que cette fonctionnalité n'existe pas, et que c'est juste une classe définie par l'utilisateur sans magie particulière. Le fait est que que nous pouvons utiliser le Bind
pour lier une variable au contenu de notre Nullable
et ensuite faire comme si rien d'étrange ne se passait. et les utiliser comme des ints normaux en les additionnant. Nous enveloppons le résultat dans un nullable à la fin, et ce nullable sera soit nul (si l'un des f
, g
ou h
retourne null) ou ce sera le résultat de la somme f
, g
et h
ensemble. (c'est analogue de la façon dont nous pouvons lier une ligne d'une base de données à une variable dans LINQ, et faire et faire des choses avec, tout en sachant que la Bind
L'opérateur s'assurera que la variable ne recevra jamais que des valeurs de lignes valides).
Vous pouvez jouer avec cela et changer n'importe quel f
, g
et h
pour retourner null et vous verrez que l'ensemble retournera null.
Il est donc clair que l'opérateur de liaison doit faire cette vérification pour nous, et se dégonfler en retournant null s'il rencontre une valeur nulle, et sinon transmettre la valeur à l'intérieur de l'objet Nullable
dans la structure lambda.
Voici le Bind
opérateur :
public static Nullable<B> Bind<A,B>( this Nullable<A> a, Func<A,Nullable<B>> f )
where B : struct
where A : struct
{
return a.HasValue ? f(a.Value) : null;
}
Les types sont ici comme dans la vidéo. Il faut un M a
( Nullable<A>
en syntaxe C# pour ce cas), et une fonction de a
à M b
( Func<A, Nullable<B>>
dans la syntaxe C#), et il renvoie un fichier M b
( Nullable<B>
).
Le code vérifie simplement si le nullable contient une valeur et si oui il l'extrait et la transmet à la fonction, sinon il renvoie simplement la valeur null. Cela signifie que la fonction Bind
se chargera de toute la logique de logique de vérification de la nullité pour nous. Si et seulement si la valeur que nous appelons Bind
on est non-nulle, alors cette valeur sera "transmise" à la fonction lambda fonction lambda, sinon nous nous retirons prématurément et l'expression entière est nulle. Cela permet au code que nous écrivons à l'aide de la monade d'être entièrement exempt de ce comportement de contrôle de nullité. entièrement libéré de ce comportement de vérification de la nullité, nous utilisons simplement Bind
et obtenir une variable liée à la valeur à l'intérieur de la valeur monadique ( fval
, gval
et hval
dans le code d'exemple) et nous pouvons les utiliser en sachant que savoir que Bind
se chargera de vérifier s'ils sont nuls avant de avant de les transmettre.
Il existe d'autres exemples de choses que vous pouvez faire avec une monade. Sur par exemple, vous pouvez faire de la Bind
opérateur s'occupe d'un flux d'entrée de caractères, et l'utiliser pour écrire des combinateurs d'analyseurs. Chaque combinateur d'analyseur peut alors être complètement inconscient de choses comme le le back-tracking, les échecs de l'analyseur etc., et simplement combiner des analyseurs plus petits comme si les choses n'allaient jamais mal se passer, tout en sachant qu'une une implémentation intelligente de Bind
trie toute la logique derrière les difficiles. Ensuite, plus tard, peut-être que quelqu'un ajoute la journalisation à la monade, mais le code utilisant la monade ne change pas, parce que toute la magie se produit dans la définition de la Bind
opérateur, le reste du code est inchangé.
Enfin, voici l'implémentation du même code en Haskell ( --
commence une ligne de commentaire).
-- Here's the data type, it's either nothing, or "Just" a value
-- this is in the standard library
data Maybe a = Nothing | Just a
-- The bind operator for Nothing
Nothing >>= f = Nothing
-- The bind operator for Just x
Just x >>= f = f x
-- the "unit", called "return"
return = Just
-- The sample code using the lambda syntax
-- that Brian showed
z = f >>= ( \fval ->
g >>= ( \gval ->
h >>= ( \hval -> return (fval+gval+hval ) ) ) )
-- The following is exactly the same as the three lines above
z2 = do
fval <- f
gval <- g
hval <- h
return (fval+gval+hval)
Comme vous pouvez le voir, la belle do
la notation à la fin le fait ressembler à du code impératif direct. Et en effet, c'est à dessein. Les monades peuvent être utilisées pour encapsuler toutes les choses utiles dans la programmation impérative (état mutable, IO etc.) et utilisées en utilisant cette belle syntaxe impérative. impérative, mais derrière les rideaux, il ne s'agit que de monades et d'une implémentation de l'opérateur bind ! Ce qui est cool, c'est que vous pouvez implémenter vos propres monades en implémentant >>=
et return
. Et si vous le faites, ces monades seront également en mesure d'utiliser les do
notation, ce qui signifie que vous pouvez écrire vos propres petits langages en définissant simplement définir deux fonctions !
0 votes
Veuillez noter qu'il s'agit en fait d'un développeur C# 3.0. Ne le confondez pas avec .NET 3.5. À part cela, bonne question.
4 votes
Il convient de souligner que les expressions de requêtes LINQ sont un exemple de comportement monadique dans C# 3.
1 votes
Je pense toujours que c'est une question double. Une des réponses dans stackoverflow.com/questions/2366/can-anyone-explain-monads lien vers channel9vip.orcsweb.com/shows/Going+Deep/ où l'un des commentaires contient un très bel exemple en C# :)
0 votes
Lire Les merveilles des monades . Je pense que c'est exactement ce que vous recherchez.
5 votes
Mais ce n'est qu'un lien d'une réponse à l'une des questions de l'OS. Je vois l'intérêt d'une question axée sur les développeurs C#. C'est quelque chose que je demanderais à un programmeur fonctionnel qui faisait du C# si j'en connaissais un, donc il semble raisonnable de le demander sur SO. Mais je respecte également votre droit à votre opinion.
1 votes
Une seule réponse ne suffit-elle pas ? ;) Ce que je veux dire, c'est que l'une des autres questions (et maintenant celle-ci aussi, alors bravo) avait une réponse spécifique à C# (qui semble vraiment bien écrite, en fait. Probablement la meilleure explication que j'ai vue).
0 votes
Duplicata possible de Qu'est-ce qu'une monade ?
0 votes
Quiconque s'intéresse aux monades à partir d'un contexte C# (ou tout autre contexte OO) ne doit pas manquer l'élément suivant excellents articles d'Eric Lippert .
0 votes
Merci pour le clin d'œil :-) Voir aussi stackoverflow.com/questions/2704652/