208 votes

En C#, qu'est-ce qu'une monade ?

On parle beaucoup des monades ces jours-ci. J'ai lu quelques articles / billets de blog, mais je n'arrive pas à aller assez loin avec leurs exemples pour bien saisir le concept. La raison est que les monades sont un concept de langage fonctionnel, et donc les exemples sont dans des langages avec lesquels je n'ai pas travaillé (puisque je n'ai pas utilisé un langage fonctionnel en profondeur). Je ne peux pas saisir la syntaxe assez profondément pour suivre les articles dans leur intégralité ... mais je peux dire qu'il y a quelque chose qui vaut la peine d'être compris.

Cependant, je connais assez bien le C#, notamment les expressions lambda et d'autres fonctionnalités. Je sais que le C# ne possède qu'un sous-ensemble de fonctionnalités fonctionnelles, et donc que les monades ne peuvent peut-être pas être exprimées en C#.

Cependant, il est sûrement possible de transmettre ce concept ? Du moins, je l'espère. Peut-être pouvez-vous présenter un exemple C# comme base, puis décrire ce qu'un développeur C# ferait souhaite qu'il pourrait faire à partir de là, mais qu'il ne peut pas faire parce que le langage manque de fonctionnalités de programmation. Ce serait fantastique, car cela permettrait de transmettre l'intention et les avantages des monades. Voici donc ma question : Quelle est la meilleure explication que vous puissiez donner des monades à un développeur C# 3 ?

Merci !

(EDIT : Au fait, je sais qu'il y a déjà au moins 3 questions "qu'est-ce qu'une monade" sur SO. Cependant, je suis confronté au même problème avec eux ... donc cette question est nécessaire imo, en raison de la concentration sur les développeurs C#. Merci.)

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# :)

161voto

sth Points 91594

La plupart de ce que vous faites en programmation toute la journée consiste à combiner certaines fonctions ensemble pour construire des fonctions plus importantes à partir d'elles. En général, vous avez non seulement des fonctions dans votre boîte à outils, mais aussi d'autres éléments tels que des opérateurs, des affectations de variables et autres, mais votre programme combine généralement de nombreux "calculs" pour en faire des calculs plus importants qui seront combinés à leur tour.

Une monade est un moyen de réaliser cette "combinaison de calculs".

En général, l'opérateur le plus basique pour combiner deux calculs est ; :

a; b

Quand vous dites cela, vous voulez dire "faites d'abord a alors, faites b ". Le résultat a; b est essentiellement un calcul qui peut être combiné avec d'autres choses. Il s'agit d'une monade simple, qui permet de combiner de petits calculs à de plus grands. Le site ; dit "fais la chose à gauche, puis fais la chose à droite".

Une autre chose que l'on peut considérer comme une monade dans les langages orientés objet est la fonction . . On trouve souvent des choses comme ça :

a.b().c().d()

Le site . signifie essentiellement "évaluer le calcul à gauche, puis appeler la méthode à droite sur le résultat de ce calcul". C'est une autre façon de combiner des fonctions/calculs ensemble, un peu plus compliquée que ; . Et le concept d'enchaîner les choses avec . est une monade, puisqu'il s'agit d'un moyen de combiner deux calculs en un nouveau calcul.

Une autre monade assez courante, qui n'a pas de syntaxe spéciale, est ce modèle :

rv = socket.bind(address, port);
if (rv == -1)
  return -1;

rv = socket.connect(...);
if (rv == -1)
  return -1;

rv = socket.send(...);
if (rv == -1)
  return -1;

Une valeur de retour de -1 indique un échec, mais il n'y a pas de véritable moyen d'abstraire cette vérification des erreurs, même si vous avez beaucoup d'appels d'API que vous devez combiner de cette manière. En fait, il s'agit simplement d'une autre monade qui combine les appels de fonction selon la règle "si la fonction de gauche a retourné -1, nous retournons nous-mêmes -1, sinon nous appelons la fonction de droite". Si nous avions un opérateur >>= qui a fait cette chose que nous pourrions simplement écrire :

socket.bind(...) >>= socket.connect(...) >>= socket.send(...)

Cela rendrait les choses plus lisibles et contribuerait à rendre abstraite notre façon particulière de combiner les fonctions, afin de ne pas avoir à nous répéter sans cesse.

Et il existe de nombreuses autres façons de combiner des fonctions/calculs qui sont utiles en tant que modèle général et qui peuvent être abstraites dans une monade, permettant à l'utilisateur de la monade d'écrire un code beaucoup plus concis et clair, puisque toute la comptabilité et la gestion des fonctions utilisées sont effectuées dans la monade.

Par exemple, l'exemple ci-dessus >>= pourrait être étendu pour "faire le contrôle d'erreur et ensuite appeler le côté droit sur la socket que nous avons reçu en entrée", de sorte que nous n'avons pas besoin de spécifier explicitement socket plusieurs fois :

new socket() >>= bind(...) >>= connect(...) >>= send(...);

La définition formelle est un peu plus compliquée, car vous devez vous préoccuper de la manière d'obtenir le résultat d'une fonction en tant qu'entrée de la suivante, si cette fonction a besoin de cette entrée et puisque vous voulez vous assurer que les fonctions que vous combinez correspondent à la manière dont vous essayez de les combiner dans votre monade. Mais le concept de base consiste simplement à formaliser différentes manières de combiner des fonctions.

32 votes

Excellente réponse ! Je vais ajouter une citation d'Oliver Steele, qui tente de faire le lien entre les monades et la surcharge d'opérateurs à la C++ ou C# : Les monades vous permettent de surcharger l'opérateur ';'.

9 votes

JörgWMittag J'ai déjà lu cette citation, mais elle m'a semblé être une absurdité trop entêtante. Maintenant que je comprends les monades et que j'ai lu cette explication sur la façon dont ';' en est une, je comprends. Mais je pense que c'est vraiment une déclaration irrationnelle pour la plupart des développeurs impératifs. Le " ; " n'est pas plus perçu comme un opérateur que le // ne l'est pour la plupart.

2 votes

Êtes-vous sûr de savoir ce qu'est une monade ? Les monades ne sont pas une "fonction" ou un calcul, il existe des règles pour les monades.

63voto

Kurt Schelfthout Points 5437

Lire Les merveilles des monades . Je pense que c'est exactement ce que vous recherchez.

48voto

Charlie Flowers Points 9145

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.

Et hier soir, je suis venu et j'ai relu ces réponses. Le plus important J'ai relu l'exemple spécifique en C# dans les commentaires du texte de la vidéo de Brian Beckman quelqu'un mentionne ci-dessus . C'était tellement clair et éclairant que j'ai décidé de le poster directement ici.

Grâce à ce commentaire, non seulement j'ai l'impression de comprendre exactement ce que sont les Monads Je me rends compte que j'ai écrit des choses en C# qui sont Monads ou du moins très proches, et s'efforçant de résoudre les mêmes problèmes.

Alors, voici le commentaire - tout ceci est une citation directe de le commentaire ici par sylvan :

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 !

3 votes

Personnellement, je préfère la version F# de la monade, mais dans tous les cas, ils sont géniaux.

3 votes

Merci de revenir ici et de mettre à jour votre message. Ce sont des suivis comme ceux-ci qui aident les programmeurs qui s'intéressent à un domaine spécifique à vraiment comprendre comment les autres programmeurs considèrent ce domaine, au lieu de simplement se baser sur "comment faire x avec y technologie". Vous êtes le meilleur !

0 votes

J'ai pris le même chemin que vous et je suis arrivé au même endroit en comprenant les monades. Cela dit, c'est la meilleure explication du comportement de liaison d'une monade que j'ai jamais vue pour un développeur impératif. Bien que je pense que vous ne touchez pas tout à fait tout ce qui concerne les monades qui est un peu plus expliqué ci-dessus par qqch.

20voto

Mike Hadlow Points 3779

J'ai commencé à écrire une série de billets sur les monades en C#.

  1. Introduction

  2. Quel est l'intérêt ?

  3. Création de notre premier monade

  4. Linq aime les monades

  5. Peut-être ;)

11voto

MarkusQ Points 15612

Une monade est essentiellement un traitement différé. Si vous essayez d'écrire un code qui a des effets secondaires (par exemple, des entrées/sorties) dans un langage qui ne les autorise pas et qui ne permet que le calcul pur, une solution consiste à dire : "D'accord, je sais que vous ne ferez pas d'effets secondaires pour moi, mais pouvez-vous calculer ce qui se passerait si vous le faisiez ?

C'est une sorte de tricherie.

Cette explication vous aidera à comprendre l'intention générale des monades, mais le diable se cache dans les détails. Comment exactement faire vous en calculez les conséquences ? Parfois, ce n'est pas joli.

La meilleure façon de donner un aperçu du comment pour une personne habituée à la programmation impérative est de dire qu'il vous place dans un DSL dans lequel les opérations qui ressemblent syntaxiquement à ce que vous avez l'habitude de faire en dehors de la monade sont utilisées pour construire une fonction qui ferait ce que vous voulez si vous pouviez (par exemple) écrire dans un fichier de sortie. C'est presque (mais pas vraiment) comme si vous construisiez du code dans une chaîne de caractères qui serait ensuite évaluée.

1 votes

Comme dans le livre "I Robot" ? Où le scientifique demande à un ordinateur de calculer le voyage dans l'espace et lui demande d'ignorer certaines règles ? : ) :) :) :)

3 votes

Hmm, une monade peut peut être utilisé pour le traitement différé et pour encapsuler les fonctions à effet secondaire, ce fut d'ailleurs sa première application réelle en Haskell, mais il s'agit en fait d'un modèle beaucoup plus générique. D'autres utilisations courantes incluent la gestion des erreurs et la gestion de l'état. Le sucre syntaxique (do en Haskell, Computation Expressions en F#, syntaxe Linq en C#) est simplement cela et fondamental pour les monades en tant que telles.

0 votes

@MikeHadlow : Les instances de la monade pour la gestion des erreurs ( Maybe et Either e ) et la gestion des états ( State s , ST s ) me semblent être des exemples particuliers de "Veuillez calculer ce qui se passerait si vous faisiez [effets secondaires pour moi]". Un autre exemple serait le nondéterminisme ( [] ).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X