Question Qu'est-ce qu'une monade?


Après avoir brièvement regardé Haskell récemment, qu'est-ce qu'un bref, succinct, pratique explication de ce qu'est essentiellement une monade?

J'ai trouvé que la plupart des explications que j'ai trouvées étaient assez inaccessibles et manquaient de détails pratiques.


1227


origine


Réponses:


Premier: Le terme monade est un peu vide si vous n'êtes pas mathématicien. Un terme alternatif est générateur de calcul ce qui est un peu plus descriptif de ce pour quoi ils sont vraiment utiles.

Vous demandez des exemples pratiques:

Exemple 1: compréhension de la liste:

[x*2 | x<-[1..10], odd x]

Cette expression renvoie les doubles de tous les nombres impairs dans la plage de 1 à 10. Très utile!

Il s'avère que c'est juste du sucre syntaxique pour certaines opérations dans la liste Monad. La même compréhension de liste peut être écrite comme:

do
   x <- [1..10]
   if odd x 
       then [x * 2] 
       else []

Ou même:

[1..10] >>= (\x -> if odd x then [x*2] else [])

Exemple 2: Entrée / Sortie:

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

Les deux exemples utilisent des monades, des constructeurs de calcul AKA. Le thème commun est que la monade opérations de chaînes de manière spécifique et utile. Dans la compréhension de liste, les opérations sont chaînées de sorte que si une opération renvoie une liste, alors les opérations suivantes sont effectuées sur chaque article dans la liste. D'autre part, la monade IO effectue les opérations de manière séquentielle, mais transmet une «variable cachée» qui représente «l'état du monde», ce qui nous permet d'écrire du code d'E / S de manière purement fonctionnelle.

Il se trouve le modèle de opérations de chaînage est très utile et est utilisé pour beaucoup de choses différentes à Haskell.

Un autre exemple est celui des exceptions: Error monad, les opérations sont chaînées de sorte qu'elles sont exécutées de manière séquentielle, sauf si une erreur est lancée, auquel cas le reste de la chaîne est abandonné.

La syntaxe list-comprehension et la do-notation sont toutes deux du sucre syntaxique pour les opérations de chaînage utilisant le >>= opérateur. Une monade est fondamentalement juste un type qui prend en charge le >>= opérateur.

Exemple 3: Un analyseur

C'est un analyseur très simple qui analyse une chaîne entre guillemets ou un nombre:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

Les opérations char, digit, etc. sont assez simples. Ils correspondent ou ne correspondent pas. La magie est la monade qui gère le flux de contrôle: Les opérations sont effectuées de manière séquentielle jusqu'à ce qu'une correspondance échoue, auquel cas la monade fait marche arrière jusqu'au dernier <|> et essaie l'option suivante. Encore une fois, une façon d'enchaîner les opérations avec une sémantique supplémentaire utile.

Exemple 4: Programmation asynchrone

Les exemples ci-dessus sont dans Haskell, mais il s'avère F# prend également en charge les monades. Cet exemple est volé à Don Syme:

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

Cette méthode récupère une page Web. La ligne de punch est l'utilisation de GetResponseAsync - il attend la réponse sur un thread séparé, tandis que le thread principal revient de la fonction. Les trois dernières lignes sont exécutées sur le thread généré lorsque la réponse a été reçue.

Dans la plupart des autres langues, vous devez créer explicitement une fonction distincte pour les lignes qui gèrent la réponse. le async Monad est capable de "diviser" le bloc par lui-même et de reporter l'exécution de la dernière moitié. (Le async {} la syntaxe indique que le flux de contrôle dans le bloc est défini par le async monade.)

Comment ils travaillent

Alors, comment une monade peut-elle faire toutes ces choses de contrôle-flux de fantaisie? Qu'est-ce qui se passe réellement dans un do-block (ou un expression de calcul comme ils sont appelés dans F #), c'est que chaque opération (fondamentalement chaque ligne) est enveloppée dans une fonction anonyme séparée. Ces fonctions sont ensuite combinées en utilisant bind opérateur (épelé >>= à Haskell). Depuis le bind l'opération combine des fonctions, elle peut les exécuter comme bon lui semble: séquentiellement, plusieurs fois, à l'envers, en rejeter quelques-unes, en exécuter une sur un fil séparé quand il en a envie et ainsi de suite.

À titre d'exemple, il s'agit de la version étendue du code IO de l'exemple 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

C'est plus laid, mais c'est aussi plus évident ce qui se passe réellement. le >>= l'opérateur est l'ingrédient magique: il prend une valeur (sur le côté gauche) et l'associe à une fonction (sur le côté droit) pour produire une nouvelle valeur. Cette nouvelle valeur est ensuite prise par le prochain >>= opérateur et à nouveau combiné avec une fonction pour produire une nouvelle valeur. >>= peut être considéré comme un mini-évaluateur.

Notez que >>= est surchargé pour différents types, de sorte que chaque monade a sa propre mise en œuvre de >>=. (Toutes les opérations dans la chaîne doivent être du type de la même monade, sinon le >>= l'opérateur ne fonctionnera pas.)

La mise en œuvre la plus simple possible >>= prend juste la valeur sur la gauche et l'applique à la fonction sur la droite et renvoie le résultat, mais comme dit précédemment, ce qui rend tout le modèle utile est quand il y a quelque chose de supplémentaire dans la mise en œuvre de la monade de >>=.

Il y a une certaine intelligence supplémentaire dans la façon dont les valeurs sont transmises d'une opération à l'autre, mais cela nécessite une explication plus approfondie du système de type Haskell.

Résumer

En termes Haskell, une monade est un type paramétré qui est une instance de la classe de type Monad, qui définit >>= avec quelques autres opérateurs. En termes simples, une monade est juste un type pour lequel le >>= l'opération est définie.

En soi >>= est juste une manière encombrante de chaîner des fonctions, mais avec la présence de la notation Do qui cache la "plomberie", les opérations monadiques se révèlent être une abstraction très agréable et utile, utiles dans de nombreux endroits du langage, et utiles pour créer vos propres mini-langues dans la langue.

Pourquoi les monades sont-elles dures?

Pour beaucoup d'apprenants Haskell, les monades sont un obstacle qu'ils frappent comme un mur de briques. Ce n'est pas que les monades elles-mêmes soient complexes, mais que l'implémentation repose sur de nombreuses autres fonctionnalités Haskell avancées comme les types paramétrés, les classes de types, etc. Le problème est que Haskell I / O est basé sur des monades, et les E / S sont probablement l'une des premières choses que vous voulez comprendre quand vous apprenez une nouvelle langue - après tout, ce n'est pas très amusant de créer des programmes qui ne produisent pas sortie. Je n'ai pas de solution immédiate pour ce problème de poule et d'oeuf, sauf traiter les E / S comme «la magie se produit ici» jusqu'à ce que vous ayez assez d'expérience avec d'autres parties du langage. Pardon.

Excellent blog sur les monades: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


965



Expliquer "qu'est-ce qu'une monade" revient à dire "qu'est-ce qu'un nombre?" Nous utilisons des chiffres tout le temps. Mais imaginez que vous avez rencontré quelqu'un qui ne savait rien sur les chiffres. Comment le Zut pourriez-vous expliquer quels sont les chiffres? Et comment pourriez-vous même commencer à décrire pourquoi cela pourrait être utile?

Qu'est-ce qu'une monade? La réponse courte: C'est une manière spécifique de chaîner les opérations ensemble.

En substance, vous écrivez des étapes d'exécution et les reliez ensemble avec la "fonction de liaison". (Dans Haskell, il est nommé >>=.) Vous pouvez écrire vous-même les appels à l'opérateur bind, ou vous pouvez utiliser la syntaxe sugar qui fait que le compilateur insère ces appels de fonction pour vous. Mais de toute façon, chaque étape est séparée par un appel à cette fonction de liaison.

Donc, la fonction de liaison est comme un point-virgule; il sépare les étapes d'un processus. Le travail de la fonction de liaison consiste à extraire la sortie de l'étape précédente et à la transmettre à l'étape suivante.

Cela ne semble pas trop dur, non? Mais il y a plus d'un sorte de monade. Pourquoi? Comment?

Eh bien, la fonction de liaison pouvez il suffit de prendre le résultat d'un pas et de le passer à l'étape suivante. Mais si c'est "tout", la monade fait ... ce n'est pas très utile. Et c'est important de comprendre: chaque utile monade fait autre chose en outre juste être une monade. Chaque utile Monad a un "pouvoir spécial", ce qui le rend unique.

(Une monade qui fait rien special s'appelle la "monade d'identité". Plutôt que la fonction d'identité, cela ressemble à une chose complètement inutile, mais s'avère ne pas être ... Mais c'est une autre histoire ™.

Fondamentalement, chaque monade a sa propre implémentation de la fonction de rattachement. Et vous pouvez écrire une fonction de reliure telle qu'elle fasse des choses entre les étapes d'exécution. Par exemple:

  • Si chaque étape renvoie un indicateur succès / échec, vous pouvez avoir bind exécuter l'étape suivante uniquement si la précédente a réussi. De cette façon, une étape défaillante interrompt automatiquement toute la séquence, sans test conditionnel de votre part. (Le Échec monade.)

  • En étendant cette idée, vous pouvez implémenter des "exceptions". (Le Erreur Monad ou Monade d'exception.) Parce que vous les définissez vous-même plutôt que d'être une fonctionnalité de langue, vous pouvez définir comment ils fonctionnent. (Par exemple, peut-être que vous voulez ignorer les deux premières exceptions et seulement abandonner quand un troisième exception est levée.)

  • Vous pouvez faire chaque retour d'étape plusieurs résultats, et ont la fonction de liaison sur eux, en alimentant chacun dans la prochaine étape pour vous. De cette manière, vous n'avez pas besoin d'écrire des boucles partout lorsque vous devez gérer plusieurs résultats. La fonction de rattachement "automatiquement" fait tout cela pour vous. (Le Liste Monad.)

  • En plus de passer un "résultat" d'une étape à l'autre, vous pouvez avoir la fonction de liaison transmettre des données supplémentaires autour aussi bien. Ces données n'apparaissent désormais plus dans votre code source, mais vous pouvez toujours y accéder de n'importe où, sans avoir à les passer manuellement à chaque fonction. (Le Lecteur Monad.)

  • Vous pouvez faire en sorte que les «données supplémentaires» peuvent être remplacées. Cela vous permet de simuler des mises à jour destructives, sans réellement faire des mises à jour destructrices. (Le État Monad et son cousin le Écrivain Monad.)

  • Parce que vous êtes seulement simuler mises à jour destructrices, vous pouvez trivialement faire des choses qui seraient impossibles avec réal mises à jour destructrices. Par exemple, vous pouvez annuler la dernière mise à jour, ou revenir à une ancienne version.

  • Vous pouvez faire une monade où les calculs peuvent être mis en pause, vous pouvez donc mettre en pause votre programme, entrer et bricoler avec des données d'état internes, puis le reprendre.

  • Vous pouvez mettre en œuvre des "continuations" en tant que monade. Cela vous permet de briser les esprits!

Tout cela et plus est possible avec des monades. Bien sûr, tout cela est aussi parfaitement possible sans pour autant monades aussi. C'est juste drastiquement Plus facile en utilisant des monades.


638



En fait, contrairement à la compréhension commune des Monades, ils n'ont rien à voir avec l'état. Les monades sont simplement un moyen d'emballer des choses et de fournir des méthodes pour faire des opérations sur les choses emballées sans les déballer.

Par exemple, vous pouvez créer un type pour envelopper un autre, dans Haskell:

data Wrapped a = Wrap a

Pour emballer des choses, nous définissons

return :: a -> Wrapped a
return x = Wrap x

Pour effectuer des opérations sans déballer, dites que vous avez une fonction f :: a -> b, alors vous pouvez le faire pour ascenseur cette fonction pour agir sur les valeurs enveloppées:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

C'est à peu près tout ce qu'il y a à comprendre. Cependant, il s'avère qu'il existe une fonction plus générale pour ce faire levage, lequel est bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bind peut faire un peu plus que fmap, mais pas vice versa. Réellement, fmap peut être défini uniquement en termes de bind et return. Donc, en définissant une monade .. vous donnez son type (ici, il était Wrapped a) puis dire comment return et bind les opérations fonctionnent.

Ce qui est cool, c'est que cela se révèle être un motif si général qu'il apparaît partout, l'encapsulation de l'état d'une manière pure n'est que l'un d'entre eux.

Pour un bon article sur la façon dont les monades peuvent être utilisées pour introduire des dépendances fonctionnelles et contrôler ainsi l'ordre d'évaluation, comme c'est le cas dans la monade IO de Haskell, consultez IO Inside.

Quant à comprendre les monades, ne vous en faites pas trop. Lisez à leur sujet ce que vous trouvez intéressant et ne vous inquiétez pas si vous ne comprenez pas tout de suite. Ensuite, il suffit de plonger dans une langue comme Haskell. Les monades sont une de ces choses où la compréhension s'infiltre dans votre cerveau par la pratique, un jour vous réalisez soudainement que vous les comprenez.


164



Mais, Tu aurais pu inventer des Monades!

Sigfpe dit:

Mais tout cela introduit des monades comme quelque chose d'ésotérique qui a besoin d'explication. Mais ce que je veux dire, c'est qu'ils ne sont pas du tout ésotériques. En effet, face à divers problèmes de programmation fonctionnelle, on aurait conduit, inexorablement, à certaines solutions, toutes des exemples de monades. En fait, j'espère vous inciter à les inventer maintenant si vous ne l'avez pas déjà fait. Il est alors un petit pas à remarquer que toutes ces solutions sont en fait la même solution déguisée. Et après avoir lu ceci, vous pourriez être dans une meilleure position pour comprendre d'autres documents sur les monades parce que vous reconnaîtrez tout ce que vous voyez comme quelque chose que vous avez déjà inventé.

Bon nombre des problèmes que les monades tentent de résoudre sont liés à la question des effets secondaires. Nous allons donc commencer avec eux. (Notez que les monades vous permettent de faire plus que de gérer les effets secondaires, en particulier de nombreux types d'objets conteneurs peuvent être considérés comme des monades.Certaines introductions aux monades ont du mal à réconcilier ces deux utilisations différentes des monades et se concentrent sur un seul L'autre.)

Dans un langage de programmation impératif tel que C ++, les fonctions ne se comportent pas comme les fonctions des mathématiques. Par exemple, supposons que nous ayons une fonction C ++ qui prend un seul argument à virgule flottante et renvoie un résultat à virgule flottante. Superficiellement, cela peut sembler un peu comme une fonction mathématique mappant les réels aux réels, mais une fonction C ++ peut faire plus que simplement renvoyer un nombre qui dépend de ses arguments. Il peut lire et écrire les valeurs des variables globales ainsi qu'écrire la sortie à l'écran et recevoir l'entrée de l'utilisateur. Dans un langage fonctionnel pur, cependant, une fonction peut seulement lire ce qui lui est fourni dans ses arguments et la seule façon dont elle peut avoir un effet sur le monde est à travers les valeurs qu'elle renvoie.


161



Une monade est un type de données qui a deux opérations: >>= (alias bind) et return (alias unit). return prend une valeur arbitraire et crée une instance de la monade avec elle. >>= prend une instance de la monade et mappe une fonction par-dessus. (Vous pouvez déjà voir qu'une monade est un type de type de données étrange, car dans la plupart des langages de programmation, vous ne pouvez pas écrire une fonction qui prend une valeur arbitraire et crée un type à partir de celle-ci. polymorphisme paramétrique.)

En notation Haskell, l'interface monad est écrite

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

Ces opérations sont censées obéir à certaines "lois", mais ce n'est pas terriblement important: les "lois" codifient simplement la façon dont les implémentations sensées des opérations doivent se comporter (essentiellement, que >>= et return devrait être d'accord sur la façon dont les valeurs se transforment en instances monad et que >>= est associatif).

Les monades ne se limitent pas à l'état et aux E / S: elles résument un modèle commun de calcul qui inclut le travail avec l'état, les E / S, les exceptions et le non-déterminisme. Les monades les plus simples à comprendre sont probablement les listes et les types d'options:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

[] et : sont les constructeurs de liste, ++est l'opérateur de concaténation, et Just et Nothing sont les Maybe constructeurs. Ces deux monades encapsulent des modèles de calcul courants et utiles sur leurs types de données respectifs (notez que ni n'a rien à voir avec les effets secondaires ou les E / S).

Vous devez vraiment jouer en écrivant un code Haskell non trivial pour apprécier ce que sont les monades et pourquoi elles sont utiles.


77



Vous devez d'abord comprendre ce qu'est un foncteur. Avant cela, comprendre les fonctions d'ordre supérieur.

UNE fonction d'ordre supérieur est simplement une fonction qui prend une fonction en argument.

UNE foncteur est toute construction de type T pour lequel il existe une fonction d'ordre supérieur, appelez-le map, qui transforme une fonction de type a -> b (donné deux types a et b) dans une fonction T a -> T b. Ce map fonction doit également obéir aux lois de l'identité et de la composition telles que les expressions suivantes soient vraies pour tous p et q (Notation Haskell):

map id = id
map (p . q) = map p . map q

Par exemple, un constructeur de type appelé List est un foncteur s'il est équipé d'une fonction de type (a -> b) -> List a -> List b qui obéit aux lois ci-dessus. La seule implémentation pratique est évidente. La résultante List a -> List b fonction itère sur la liste donnée, appelant le (a -> b) fonction pour chaque élément et renvoie la liste des résultats.

UNE monade est essentiellement juste un foncteur T avec deux méthodes supplémentaires, join, de type T (T a) -> T a, et unit (appelé quelques fois return, fork, ou pure) de type a -> T a. Pour les listes dans Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Pourquoi est-ce utile? Parce que vous pourriez, par exemple, map sur une liste avec une fonction qui renvoie une liste. Join prend la liste résultante des listes et les concatène. List est une monade parce que c'est possible.

Vous pouvez écrire une fonction qui fait map, puis join. Cette fonction est appelée bind, ou flatMap, ou (>>=), ou (=<<). C'est normalement comment une instance de monad est donnée dans Haskell.

Une monade doit satisfaire à certaines lois, à savoir que join doit être associatif. Cela signifie que si vous avez une valeur x de type [[[a]]] puis join (join x) devrait être égal join (map join x). Et pure doit être une identité pour join tel que join (pure x) == x.


70



[Disclaimer: J'essaie toujours de graver complètement des monades. Ce qui suit est juste ce que j'ai compris jusqu'à présent. Si c'est faux, j'espère que quelqu'un de compétent m'appellera sur le tapis.]

Arnar a écrit:

Les monades sont simplement un moyen d'emballer des choses et de fournir des méthodes pour faire des opérations sur les choses emballées sans les déballer.

C'est exactement ça. L'idée va comme ceci:

  1. Vous prenez une sorte de valeur et l'enveloppez avec quelques informations supplémentaires. Tout comme la valeur est d'un certain type (par exemple un entier ou une chaîne), les informations supplémentaires sont d'un certain type.

    Par exemple, cette information supplémentaire pourrait être un Maybe ou un IO.

  2. Ensuite, vous avez certains opérateurs qui vous permettent d'exploiter les données enveloppées tout en transportant ces informations supplémentaires. Ces opérateurs utilisent les informations supplémentaires pour décider comment modifier le comportement de l'opération sur la valeur enveloppée.

    Par exemple, un Maybe Int peut être un Just Int ou Nothing. Maintenant, si vous ajoutez un Maybe Int à un Maybe Int, l'opérateur va vérifier pour voir si elles sont à la fois Just Ints à l'intérieur, et si oui, va déballer le Ints, passez-leur l'opérateur d'addition, re-envelopper le résultant Int dans une nouvelle Just Int (qui est valide Maybe Int), et donc retourner un Maybe Int. Mais si l'un d'entre eux était un Nothing à l'intérieur, cet opérateur va juste revenir immédiatement Nothing, ce qui est encore valide Maybe Int. De cette façon, vous pouvez prétendre que votre Maybe Ints sont juste des nombres normaux et effectuent des calculs réguliers sur eux. Si vous deviez obtenir un Nothing, vos équations produiront toujours le bon résultat - sans que vous ayez à jeter des chèques pour Nothing partout.

Mais l'exemple est juste ce qui se passe pour Maybe. Si l'information supplémentaire était un IO, alors cet opérateur spécial défini pour IOs serait appelé à la place, et il pourrait faire quelque chose de totalement différent avant d'effectuer l'addition. (OK, en ajoutant deux IO Ints ensemble est probablement absurde - je ne suis pas sûr encore.) (Aussi, si vous avez prêté attention à la Maybe Par exemple, vous avez remarqué que "envelopper une valeur avec des trucs supplémentaires" n'est pas toujours correct. Mais il est difficile d'être exact, correct et précis sans être impénétrable.)

Fondamentalement, "Monad" signifie grosso modo "motif". Mais au lieu d'un livre plein de motifs informellement expliqués et spécifiquement nommés, vous avez maintenant une construction de la langue - syntaxe et tout - qui vous permet de déclarer de nouveaux modèles en tant que choses dans votre programme. (L'imprécision ici est que tous les modèles doivent suivre une forme particulière, donc une monade n'est pas aussi générique qu'un modèle, mais je pense que c'est le terme le plus proche que la plupart des gens connaissent et comprennent.)

Et c'est pourquoi les gens trouvent les monades si confuses: parce qu'elles sont un concept générique. Demander ce qui fait qu'une monade est quelque chose d'aussi vague que de demander ce qui fait que quelque chose est un modèle.

Mais pensez aux implications d'avoir un support syntaxique dans le langage pour l'idée d'un motif: au lieu d'avoir à lire le Gang of Four réserver et mémoriser la construction d'un motif particulier, vous venez écrire du code qui implémente ce modèle d'une manière agnostique et générique une fois et alors vous avez terminé! Vous pouvez ensuite réutiliser ce modèle, comme Visiteur ou Stratégie ou Façade ou autre, juste en décochant les opérations dans votre code sans avoir à le ré-implémenter encore et encore!

Voilà pourquoi les gens qui comprendre les monades les trouvent si utile: ce n'est pas un concept de tour d'ivoire que les snobs intellectuels se targuent de comprendre (OK, ça aussi, bien sûr, teehee), mais rend le code plus simple.


42



Après beaucoup d'effort, je pense que je comprends enfin la monade. Après avoir relu ma propre longue critique de la réponse majoritairement la plus élevée, je vais offrir cette explication.

Il y a trois questions auxquelles il faut répondre pour comprendre les monades:

  1. Pourquoi avez-vous besoin d'une monade?
  2. Qu'est-ce qu'une monade?
  3. Comment une monade est-elle mise en œuvre?

Comme je l'ai noté dans mes commentaires originaux, trop d'explications de monades se retrouvent dans la question numéro 3, sans, et avant de couvrir de manière adéquate la question 2, ou la question 1.

Pourquoi avez-vous besoin d'une monade?

Les langages fonctionnels purs comme Haskell sont différents des langages impératifs comme C ou Java en ce sens qu'un programme purement fonctionnel n'est pas nécessairement exécuté dans un ordre spécifique, une étape à la fois. Un programme Haskell est plus proche d'une fonction mathématique, dans laquelle vous pouvez résoudre l '"équation" dans un nombre quelconque d'ordres potentiels. Cela confère un certain nombre d'avantages, parmi lesquels il élimine la possibilité de certains types de bogues, en particulier ceux relatifs à des choses comme «état».

Cependant, il y a certains problèmes qui ne sont pas si simples à résoudre avec ce style de programmation. Certaines choses, comme la programmation de la console et les entrées / sorties de fichiers, nécessitent que les choses se passent dans un ordre particulier ou nécessitent un état. Une façon de résoudre ce problème consiste à créer une sorte d'objet qui représente l'état d'un calcul, et une série de fonctions qui prennent un objet d'état en entrée et retournent un nouvel objet d'état modifié.

Créons donc une valeur "state" hypothétique, qui représente l'état d'un écran de console. exactement comment cette valeur est construite n'est pas importante, mais disons que c'est un tableau de caractères ASCII de longueur octets qui représente ce qui est actuellement visible sur l'écran, et un tableau qui représente la dernière ligne d'entrée entrée par l'utilisateur, en pseudocode. Nous avons défini certaines fonctions qui prennent l'état de la console, le modifient et retournent un nouvel état de la console.

consolestate MyConsole = new consolestate;

Donc, pour faire de la programmation en console, mais d'une manière purement fonctionnelle, vous auriez besoin d'imbriquer beaucoup d'appels de fonctions entre eux.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

La programmation de cette manière conserve le style fonctionnel "pur", tout en forçant les changements à la console à se produire dans un ordre particulier. Mais, nous voudrons probablement faire plus que quelques opérations à la fois comme dans l'exemple ci-dessus. Les fonctions d'imbrication de cette manière commenceront à devenir disgracieuses. Ce que nous voulons, c'est du code qui fait essentiellement la même chose que ci-dessus, mais qui s'écrit un peu plus comme ceci:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

Ce serait en effet un moyen plus pratique de l'écrire. Comment faisons-nous cela?

Qu'est-ce qu'une monade?

Une fois que vous avez un type (tel que consolestate) que vous définissez avec un tas de fonctions conçues spécifiquement pour fonctionner sur ce type, vous pouvez transformer l'ensemble de ces choses en une "monade" en définissant un opérateur comme : (bind) qui alimente automatiquement les valeurs de retour à sa gauche, en paramètres de fonction sur sa droite, et un lift opérateur qui transforme les fonctions normales en fonctions qui fonctionnent avec ce type spécifique d'opérateur de liaison.

Comment une monade est-elle mise en œuvre?

Voir d'autres réponses, qui semblent assez libres de sauter dans les détails de cela.


37