haskell - Qu'est-ce qu'une monade?

Translate

Après avoir brièvement examiné Haskell récemment, ce qui serait unbref, succinct, pratiqueexplication de ce qu'est essentiellement une monade?

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

This question and all comments follow the "Attribution Required."

Toutes les réponses

Translate

Premièrement: le termemonadeest un peu vide si vous n'êtes pas mathématicien. Un autre terme estconstructeur de calculqui est un peu plus descriptif de ce à quoi ils sont réellement utiles.

Vous demandez des exemples pratiques:

Exemple 1: Compréhension de liste:

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

Cette expression renvoie les doubles de tous les nombres impairs compris entre 1 et 10. Très utile!

Il s'avère que ce n'est vraiment que du sucre syntaxique pour certaines opérations au sein de la monade List. La même compréhension de liste peut être écrite comme suit:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

Ou même:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

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 monadeopérations de chaînesd'une manière spécifique et utile. Dans la compréhension de liste, les opérations sont enchaînées de telle sorte que si une opération retourne une liste, les opérations suivantes sont effectuées surchaque articledans la liste. La monade IO, quant à elle, effectue les opérations séquentiellement, mais transmet une "variable cachée", qui représente "l'état du monde", ce qui nous permet d'écrire du code I / O d'une manière purement fonctionnelle.

Il s'avère que le modèle deopérations de chaînageest assez utile et est utilisé pour beaucoup de choses différentes dans Haskell.

Un autre exemple est celui des exceptions: l'utilisation duErrormonade, les opérations sont chaînées de telle sorte qu'elles sont exécutées séquentiellement, sauf si une erreur est émise, auquel cas le reste de la chaîne est abandonné.

La syntaxe de compréhension de liste et la notation do sont des sucres syntaxiques pour les opérations de chaînage utilisant le>>=opérateur. Une monade est essentiellement un type qui prend en charge le>>=opérateur.

Exemple 3: un analyseur

Il s'agit d'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érationschar, 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 séquentiellement jusqu'à ce qu'une correspondance échoue, auquel cas la monade revient à la dernière<|>et essaie l'option suivante. Encore une fois, une manière d'enchaîner les opérations avec une sémantique supplémentaire et utile.

Exemple 4: programmation asynchrone

Les exemples ci-dessus sont en Haskell, mais il s'avèreF#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 punch line est l'utilisation deGetResponseAsync- il attend en fait 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. leasyncmonad est capable de «scinder» le bloc tout seul et de reporter l'exécution de la seconde moitié. (Leasync {}La syntaxe indique que le flux de contrôle dans le bloc est défini par leasyncmonade.)

Comment ils travaillent

Alors, comment une monade peut-elle faire toutes ces choses fantaisistes de flux de contrôle? Que se passe-t-il réellement dans un do-block (ou unexpression de calculcomme ils sont appelés en F #), est que chaque opération (essentiellement chaque ligne) est enveloppée dans une fonction anonyme distincte. Ces fonctions sont ensuite combinées à l'aide dubindopérateur (orthographié>>=à Haskell). Depuis lebindL'opération combine des fonctions, elle peut les exécuter comme bon lui semble: séquentiellement, plusieurs fois, à l'envers, en supprimer, en exécuter sur un thread séparé quand il en a envie, etc.

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

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

C'est plus moche, 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 la combine avec une fonction (sur le côté droit), pour produire une nouvelle valeur. Cette nouvelle valeur est alors prise par la suivante>>=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 implémentation de>>=. (Toutes les opérations de 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 de>>=prend juste la valeur de gauche et l'applique à la fonction de droite et renvoie le résultat, mais comme dit précédemment, ce qui rend l'ensemble du modèle utile, c'est quand il y a quelque chose de plus dans l'implémentation 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 n'est qu'un type pour lequel le>>=l'opération est définie.

En soi>>=est juste une manière encombrante de chaîner les 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, utile à de nombreux endroits dans le langage, et utile pour créer vos propres mini-langues dans la langue.

Pourquoi les monades sont-elles dures?

Pour de nombreux apprenants Haskell, les monades sont un obstacle qu'ils heurtent 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 avancées de Haskell telles que les types paramétrés, les classes de types, etc. Le problème est que les E / S Haskell sont basées sur des monades, et les E / S sont probablement l'une des premières choses que vous voulez comprendre lors de l'apprentissage d'une nouvelle langue - après tout, ce n'est pas très amusant de créer des programmes qui n'en produisent pas. production. Je n'ai pas de solution immédiate pour ce problème de la poule et de l'œuf, sauf de traiter les E / S comme "la magie se produit ici" jusqu'à ce que vous ayez suffisamment d'expérience avec d'autres parties du langage. Désolé.

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

La source
Translate

Expliquer "qu'est-ce qu'une monade", c'est un peu comme dire "qu'est-ce qu'un nombre?" Nous utilisons des nombres tout le temps. Mais imaginez que vous rencontriez quelqu'un qui ne savait rien des chiffres. Comment leZutexpliqueriez-vous quels sont les nombres? Et comment commenceriez-vous même à décrire pourquoi cela pourrait être utile?

Qu'est-ce qu'une monade? La réponse courte: c'est une manière spécifique d'enchaîner les opérations.

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

Ainsi, 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 à prendre la sortie de l'étape précédente et à la transmettre à l'étape suivante.

Cela ne semble pas trop difficile, non? Mais il y aplus d'unsorte de monade. Pourquoi? Comment?

Eh bien, la fonction de liaisonpouvezprenez simplement le résultat d'une étape et transmettez-le à l'étape suivante. Mais si c'est "tout" la monade fait ... ce n'est en fait pas très utile. Et il est important de comprendre: chaqueutilela monade fait autre choseen plusêtre juste une monade. Chaqueutilela monade a un «pouvoir spécial», ce qui la rend unique.

(Une monade qui faitrienspécial est appelé la "monade d'identité". Un peu comme la fonction d'identité, cela semble être une chose totalement inutile, mais s'avère ne pas l'être ... Mais c'est une autre histoire ™.)

Fondamentalement, chaque monade a sa propre implémentation de la fonction de liaison. Et vous pouvez écrire une fonction de liaison de manière à ce qu'elle fasse des choses bizarres entre les étapes d'exécution. Par exemple:

  • Si chaque étape renvoie un indicateur de réussite / échec, vous pouvez demander à bind d'exécuter l'étape suivante uniquement si la précédente a réussi. De cette façon, une étape qui échoue abandonne la séquence entière "automatiquement", sans aucun test conditionnel de votre part. (LeMonade d'échec.)

  • En prolongeant cette idée, vous pouvez implémenter des «exceptions». (LeErreur MonadouMonade d'exception.) Parce que vous les définissez vous-même plutôt que d'être une fonctionnalité de langage, vous pouvez définir leur fonctionnement. (Par exemple, vous souhaitez peut-être ignorer les deux premières exceptions et abandonner uniquement lorsqu'untroisièmel'exception est levée.)

  • Vous pouvez faire revenir chaque étaperésultats multiples, et placez la fonction de liaison en boucle sur eux, alimentant chacun d'entre eux dans l'étape suivante pour vous. De cette façon, vous n'avez pas à continuer à écrire des boucles partout lorsque vous traitez avec plusieurs résultats. La fonction bind fait "automatiquement" tout cela pour vous. (LeListe Monad.)

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

  • Vous pouvez faire en sorte que les "données supplémentaires" puissent être remplacées. Cela vous permet desimuler des mises à jour destructives, sans faire de mises à jour destructrices. (LeMonade d'Étatet son cousin leÉcrivain Monad.)

  • Parce que tu es seulementsimulermises à jour destructives, vous pouvez faire des choses qui ne seraient pas possiblesréelmises à jour destructrices. Par exemple, vous pouvezannuler la dernière mise à jour, ourevenir à une ancienne version.

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

  • Vous pouvez implémenter des "continuations" en tant que monade. Cela vous permet debriser les esprits!

Tout cela et plus encore est possible avec les monades. Bien sûr, tout cela est également parfaitement possiblesans pour autantles monades aussi. C'est juste radicalementPlus facileen utilisant des monades.

La source
Translate

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 fournissent des méthodes pour effectuer des opérations sur les choses emballées sans les déballer.

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

data Wrapped a = Wrap a

Pour envelopper les choses, nous définissons

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

Pour effectuer des opérations sans déballer, disons que vous avez une fonctionf :: a -> b, alors vous pouvez le faire pourascenseurcette fonction pour agir sur les valeurs encapsulé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 le fairelevage, lequel estbind:

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

bindpeut faire un peu plus quefmap, mais pas l'inverse. Réellement,fmapne peut être défini qu'en termes debindetreturn. Donc, lors de la définition d'une monade .. vous donnez son type (ici c'étaitWrapped a) puis dites comment sonreturnetbindles opérations fonctionnent.

Ce qui est cool, c'est que cela s'avère être un modèle si général qu'il apparaît partout, l'état d'encapsulation de 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 ainsi contrôler l'ordre d'évaluation, comme il est utilisé dans la monade IO de Haskell, consultezIO à l'intérieur.

Quant à la compréhension des 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. Alors plonger dans une langue comme Haskell est la voie à suivre. Les monades sont l'une de ces choses où la compréhension pénètre dans votre cerveau par la pratique, un jour, vous réalisez soudainement que vous les comprenez.

La source
Translate

Mais,Vous auriez pu inventer les Monades!

sigfpe dit:

Mais tous ces éléments présentent les monades comme quelque chose d'ésotérique qui a besoin d'explications. Mais ce que je veux dire, c'est qu'ils ne sont pas du tout ésotériques. En fait, face à divers problèmes de programmation fonctionnelle, vous auriez été conduit, inexorablement, à certaines solutions, qui sont toutes des exemples de monades. En fait, j'espère vous amener à les inventer maintenant si vous ne l'avez pas déjà fait. C'est alors un petit pas de constater que toutes ces solutions sont en fait la même solution déguisée. Et après avoir lu ceci, vous serez peut-être mieux placé pour comprendre d'autres documents sur les monades, car 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 par 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 des introductions aux monades ont du mal à concilier ces deux utilisations différentes des monades et à se concentrer sur une seule ou L'autre.)

Dans un langage de programmation impératif tel que C ++, les fonctions ne se comportent en rien comme les fonctions des mathématiques. Par exemple, supposons que nous ayons une fonction C ++ qui prend un seul argument en virgule flottante et renvoie un résultat en virgule flottante. Superficiellement, cela peut ressembler un peu à une fonction mathématique mappant des réels à des 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 les entrées de l'utilisateur. Dans un langage fonctionnel pur, cependant, une fonction ne peut lire que ce qui lui est fourni dans ses arguments et la seule façon pour elle d'avoir un effet sur le monde est à travers les valeurs qu'elle renvoie.

La source
Translate

Une monade est un type de données qui comporte deux opérations:>>=(aliasbind) etreturn(aliasunit).returnprend une valeur arbitraire et crée une instance de la monade avec elle.>>=prend une instance de la monade et mappe une fonction dessus. (Vous pouvez déjà voir qu'une monade est un type étrange de type de données, 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. Les monades utilisent une sorte depolymorphisme paramétrique.)

En notation Haskell, l'interface monade s'écrit

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 devraient se comporter (fondamentalement, que>>=etreturndoit convenir de la manière dont les valeurs sont transformées en instances de monades et>>=est associatif).

Les monades ne concernent pas seulement l'état et les E / S: elles font abstraction d'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, etJustetNothingsont lesMaybeconstructeurs. Ces deux monades encapsulent des modèles de calcul communs et utiles sur leurs types de données respectifs (notez qu'aucune des deux n'a rien à voir avec les effets secondaires ou les E / S).

Vous devez vraiment jouer autour de l'écriture de code Haskell non trivial pour apprécier ce que sont les monades et pourquoi elles sont utiles.

La source
Translate

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

A fonction d'ordre supérieurest simplement une fonction qui prend une fonction comme argument.

A foncteurest n'importe quel type de constructionTpour laquelle il existe une fonction d'ordre supérieur, appelez-lamap, qui transforme une fonction de typea -> b(étant donné deux typesaetb) en une fonctionT a -> T b. Cemapla fonction doit également obéir aux lois d'identité et de composition telles que les expressions suivantes retournent vrai pour touspetq(Notation Haskell):

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

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

A monadeest essentiellement juste un foncteurTavec deux méthodes supplémentaires,join, de typeT (T a) -> T a, etunit(appelé quelques foisreturn, fork, oupure) de typea -> T a. Pour les listes dans Haskell:

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

Pourquoi est-ce utile? Parce que vous pourriez, par exemple,mapsur une liste avec une fonction qui renvoie une liste.Joinprend la liste de listes résultante et les concatène.Listest une monade parce que c'est possible.

Vous pouvez écrire une fonction qui faitmap, puisjoin. Cette fonction s'appellebind, ouflatMap, ou(>>=), ou(=<<). C'est normalement ainsi qu'une instance de monade est donnée dans Haskell.

Une monade doit satisfaire certaines lois, à savoir quejoindoit être associatif. Cela signifie que si vous avez une valeurxde type[[[a]]]puisjoin (join x)devrait égalerjoin (map join x). Etpuredoit être une identité pourjointel quejoin (pure x) == x.

La source
Translate

[Clause de non-responsabilité: j'essaie toujours de faire pleinement groover les monades. Ce qui suit est exactement ce que j'ai compris jusqu'à présent. Si c'est faux, j'espère que quelqu'un de bien informé m'appellera sur le tapis.]

Arnar a écrit:

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

C'est précisément ça. L'idée va comme ceci:

  1. Vous prenez une sorte de valeur et l'enveloppez avec des 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, ces informations supplémentaires pourraient êtreMaybeou unIO.

  2. Ensuite, vous avez quelques opérateurs qui vous permettent d'opérer sur 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 encapsulée.

    Par exemple, unMaybe Intpeut être unJust IntouNothing. Maintenant, si vous ajoutez unMaybe Intà unMaybe Int, l'opérateur vérifiera s'ils sont tous les deuxJust Ints à l'intérieur, et si tel est le cas, déballera leInts, passez-leur l'opérateur d'addition, remballez le résultatIntdans un nouveauJust Int(qui est unMaybe Int), et donc renvoyer unMaybe Int. Mais si l'un d'eux était unNothingà l'intérieur, cet opérateur retournera immédiatementNothing, qui est encore une fois unMaybe Int. De cette façon, vous pouvez prétendre que votreMaybe IntLes s ne sont que des nombres normaux et effectuent des calculs réguliers dessus. Si vous deviez obtenir unNothing, vos équations produiront toujours le bon résultat -sans que vous ayez à jeter des chèques pourNothingpartout.

Mais l'exemple est exactement ce qui se passe pourMaybe. Si les informations supplémentaires étaient unIO, alors cet opérateur spécial défini pourIOs serait appelé à la place, et il pourrait faire quelque chose de totalement différent avant d'effectuer l'addition. (OK, en ajoutant deuxIO Ints ensemble est probablement insensé - je ne suis pas encore sûr.) (Aussi, si vous avez prêté attention auMaybePar exemple, vous avez remarqué que «envelopper une valeur avec des éléments supplémentaires» n'est pas toujours correct. Mais il est difficile d'être exact, correct et précis sans être impénétrable.)

Fondamentalement,"Monade" signifie à peu près "motif". Mais au lieu d'un livre plein de modèles expliqués de manière informelle et nommés spécifiquement, vous avez maintenantune construction de langage- la syntaxe et tout - qui vous permet dedéclarer de nouveaux modèles comme des éléments de 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 déroutantes: parce qu'elles sont un concept tellement générique. Demander ce qui fait de quelque chose une monade est tout aussi vague que demander ce qui fait de quelque chose 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 leGang des quatreréserver et mémoriser la construction d'un modèle particulier, il vous suffitécrire du code qui implémente ce modèle de manière agnostique et génériqueune fois et puis vous avez terminé! Vous pouvez ensuite réutiliser ce modèle, comme Visiteur ou Stratégie ou Façade ou autre, simplement en décorant les opérations de votre code avec lui, sans avoir à le réimplémenter encore et encore!

C'est pourquoi les gens quicomprendreles monades les trouvent ainsiutile: 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 cela simplifie en fait le code.

La source
Translate

Après de nombreux efforts, je pense avoir enfin compris la monade. Après avoir relu ma propre longue critique de la réponse très majoritairement votée, je proposerai 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 implémentée?

Comme je l'ai noté dans mes commentaires originaux, trop d'explications de la monade se retrouvent dans la question numéro 3, sans et avant de vraiment couvrir 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 qu'un programme purement fonctionnel n'est pas nécessairement exécuté dans un ordre spécifique, une étape à la fois. Un programme Haskell s'apparente davantage à une fonction mathématique, dans laquelle vous pouvez résoudre «l'équation» dans n'importe quel nombre 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 liés à des choses comme "état".

Cependant, il existe 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 produisent dans un ordre particulier ou doivent conserver leur état. Une façon de résoudre ce problème consiste à créer un type 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 "état" hypothétique, qui représente l'état d'un écran de console. la manière exacte dont cette valeur est construite n'est pas importante, mais disons que c'est un tableau de caractères ascii de longueur d'octet qui représente ce qui est actuellement visible à 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 renvoient un nouvel état de la console.

consolestate MyConsole = new consolestate;

Donc, pour faire de la programmation de la console, mais d'une manière purement fonctionnelle, vous auriez besoin d'imbriquer beaucoup d'appels de fonctions les uns dans les autres.

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 obligeant les modifications de 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 une manière plus pratique de l'écrire. Mais comment faire ça?

Qu'est-ce qu'une monade?

Une fois que vous avez un type (tel queconsolestate) que vous définissez avec un tas de fonctions conçues spécifiquement pour opérer 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, dans les paramètres de fonction à sa droite, et unliftopé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 implémentée?

Voir d'autres réponses, qui semblent tout à fait libres de sauter dans les détails.

La source
Translate

(Voir aussi les réponses surQu'est-ce qu'une monade?)

Une bonne motivation pour les Monades est sigfpe (Dan Piponi)Vous auriez pu inventer des monades! (Et peut-être que vous l'avez déjà). Il y aBEAUCOUP d'autres tutoriels monades, dont beaucoup tentent à tort d'expliquer les monades en «termes simples» en utilisant diverses analogies: c'est leerreur de didacticiel monade; évite-les.

Comme le dit DR MacIver dansDites-nous pourquoi votre langue est nulle:

Donc, les choses que je déteste chez Haskell:

Commençons par l'évidence. Tutoriels Monad. Non, pas des monades. Plus précisément les tutoriels. Ils sont infinis, exagérés et cher dieu sont-ils ennuyeux. De plus, je n'ai jamais vu de preuves convaincantes qu'ils aident réellement. Lisez la définition de la classe, écrivez du code, surmontez le nom effrayant.

Vous dites que vous comprenez la monade peut-être? Bien, vous êtes sur votre chemin. Commencez simplement à utiliser d'autres monades et tôt ou tard, vous comprendrez ce que sont les monades en général.

[Si vous êtes orienté mathématiquement, vous voudrez peut-être ignorer les dizaines de tutoriels et apprendre la définition, ou suivrecours de théorie des catégories:) La partie principale de la définition est qu'un Monad M implique un "constructeur de type" qui définit pour chaque type "T" existant un nouveau type "MT", et quelques façons d'aller et venir entre les types "réguliers" et " Types M ".]

Aussi, de manière assez surprenante, l'une des meilleures introductions aux monades est en fait l'un des premiers articles académiques présentant les monades, Philip Wadler'sMonades pour la programmation fonctionnelle. Il a en fait pratique,non trivialdes exemples motivants, contrairement à de nombreux didacticiels artificiels.

La source
Translate

Une monade est, en fait, une forme d '«opérateur de type». Cela fera trois choses. Tout d'abord, il "encapsulera" (ou convertira autrement) une valeur d'un type en un autre type (généralement appelé "type monadique"). Deuxièmement, il rendra toutes les opérations (ou fonctions) disponibles sur le type sous-jacent disponibles sur le type monadique. Enfin, il fournira un support pour se combiner avec une autre monade pour produire une monade composite.

Le «peut-être monade» est essentiellement l'équivalent des «types Nullable» dans Visual Basic / C #. Il prend un type non Nullable "T" et le convertit en "Nullable <T>", puis définit ce que tous les opérateurs binaires signifient sur un Nullable <T>.

Les effets secondaires sont représentés de manière similaire. Une structure est créée qui contient les descriptions des effets secondaires à côté de la valeur de retour d'une fonction. Les opérations «levées» se copient ensuite autour des effets secondaires lorsque les valeurs sont transmises entre les fonctions.

Ils sont appelés "monades" plutôt que le nom plus facile à saisir des "opérateurs de type" pour plusieurs raisons:

  1. Les monades ont des restrictions sur ce qu'elles peuvent faire (voir la définition pour plus de détails).
  2. Ces restrictions, ainsi que le fait qu'il y a trois opérations impliquées, sont conformes à la structure de ce qu'on appelle une monade dans la théorie des catégories, qui est une branche obscure des mathématiques.
  3. Ils ont été conçus par des partisans de langages fonctionnels «purs»
  4. Les partisans des langages fonctionnels purs comme les branches obscures des mathématiques
  5. Parce que les mathématiques sont obscures et que les monades sont associées à des styles de programmation particuliers, les gens ont tendance à utiliser le mot monade comme une sorte de poignée de main secrète. Pour cette raison, personne n'a pris la peine d'investir dans un meilleur nom.
La source
Translate

Après avoir répondu à cette question il y a quelques années, je pense que je peux améliorer et simplifier cette réponse avec ...

Une monade est une technique de composition de fonctions qui externalise le traitement de certains scénarios d'entrée à l'aide d'une fonction de composition,bind, pour prétraiter l'entrée pendant la composition.

Dans une composition normale, la fonction,compose (>>), est utilisé pour appliquer la fonction composée au résultat de son prédécesseur en séquence. Il est important de noter que la fonction en cours de composition doit gérer tous les scénarios de son entrée.

(x -> y) >> (y -> z)

Cette conception peut être améliorée en restructurant l'entrée afin que les états pertinents soient plus facilement interrogés. Donc, au lieu de simplementyla valeur peut devenirMbcomme, par exemple,(is_OK, b)siyinclus une notion de validité.

Par exemple, lorsque l'entrée n'est que possiblement un nombre, au lieu de renvoyer une chaîne qui peut contenir consciencieusement un nombre ou non, vous pouvez restructurer le type en unboolindiquant la présence d'un nombre valide et d'un nombre dans le tuple tel que,bool * float. Les fonctions composées n'auraient plus besoin d'analyser une chaîne d'entrée pour déterminer si un nombre existe, mais pourraient simplement inspecter leboolpartie d'un tuple.

(Ma -> Mb) >> (Mb -> Mc)

Ici encore, la composition se fait naturellement aveccomposeet donc chaque fonction doit gérer tous les scénarios de son entrée individuellement, bien que cela soit maintenant beaucoup plus facile.

Cependant, que se passerait-il si nous pouvions externaliser l'effort d'interrogation pour les moments où la gestion d'un scénario est une routine. Par exemple, que se passe-t-il si notre programme ne fait rien quand l'entrée n'est pas OK comme quandis_OKestfalse. Si cela était fait, les fonctions composées n'auraient pas besoin de gérer elles-mêmes ce scénario, simplifiant considérablement leur code et effectuant un autre niveau de réutilisation.

Pour réaliser cette externalisation nous pourrions utiliser une fonction,bind (>>=), pour effectuer lecompositionau lieu decompose. En tant que tel, au lieu de simplement transférer des valeurs de la sortie d'une fonction à l'entrée d'une autreBindinspecterait leMportion deMaet décidez si et comment appliquer la fonction composée aua. Bien sûr, la fonctionbindserait défini spécifiquement pour notre particulierMafin de pouvoir inspecter sa structure et réaliser le type d'application souhaité. Néanmoins, leapeut être n'importe quoi depuisbindpasse simplement leanon inspecté à la fonction composée quand il détermine l'application nécessaire. De plus, les fonctions composées elles-mêmes n'ont plus besoin de traiterMpartie de la structure d'entrée non plus, les simplifiant. Par conséquent...

(a -> Mb) >>= (b -> Mc)ou plus succinctementMb >>= (b -> Mc)

En bref, une monade externalise et fournit ainsi un comportement standard autour du traitement de certains scénarios d'entrée une fois que l'entrée est conçue pour les exposer suffisamment. Cette conception est unshell and contentmodèle où le shell contient des données pertinentes pour l'application de la fonction composée et est interrogé par et reste uniquement disponible pour lebindfonction.

Par conséquent, une monade est trois choses:

  1. unMcoque pour contenir les informations pertinentes de la monade,
  2. a bindfonction implémentée pour utiliser ces informations du shell dans son application des fonctions composées à la ou aux valeurs de contenu qu'il trouve dans le shell, et
  3. fonctions composables du formulaire,a -> Mb, produisant des résultats qui incluent des données de gestion monadiques.

D'une manière générale, l'entrée d'une fonction est beaucoup plus restrictive que sa sortie qui peut inclure des éléments tels que des conditions d'erreur; d'où leMbla structure des résultats est généralement très utile. Par exemple, l'opérateur de division ne renvoie pas de nombre lorsque le diviseur est0.

Aditionellement,monads peuvent inclure des fonctions d'enroulement qui encapsulent des valeurs,a, dans le type monadique,Ma, et fonctions générales,a -> b, en fonctions monadiques,a -> Mb, en enveloppant leurs résultats après application. Bien sûr, commebind, ces fonctions d'enroulement sont spécifiques àM. Un exemple:

let return a = [a]
let lift f a = return (f a)

La conception dubindla fonction suppose des structures de données immuables et des fonctions pures, d'autres choses deviennent complexes et des garanties ne peuvent être faites. En tant que tel, il existe des lois monadiques:

Donné...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Ensuite...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitysignifie quebindpréserve l'ordre d'évaluation quel que soit le momentbindest appliqué. Autrement dit, dans la définition deAssociativityci-dessus, l'évaluation précoce de la force dubindingdefetgne résultera qu'en une fonction qui attendMaafin de compléter lebind. D'où l'évaluation deMadoit être déterminée avant que sa valeur puisse être appliquée àfet ce résultat à son tour appliqué àg.

La source
Tim Lee
Translate

Les monades doivent contrôler le flux ce que les types de données abstraits sont aux données.

En d'autres termes, de nombreux développeurs sont à l'aise avec l'idée d'ensembles, de listes, de dictionnaires (ou de hachages, ou de cartes) et d'arbres. Dans ces types de données, il existe de nombreux cas particuliers (par exemple InsertionOrderPreservingIdentityHashMap).

Cependant, lorsqu'ils sont confrontés au «flux» du programme, de nombreux développeurs n'ont pas été exposés à beaucoup plus de constructions que if, switch / case, do, while, goto (grr) et (peut-être) fermetures.

Ainsi, une monade est simplement une construction de flux de contrôle. Une meilleure expression pour remplacer la monade serait «type de contrôle».

En tant que telle, une monade a des emplacements pour la logique de contrôle, des instructions ou des fonctions - l'équivalent dans les structures de données serait de dire que certaines structures de données vous permettent d'ajouter des données et de les supprimer.

Par exemple, la monade "si":

if( clause ) then block

dans sa forme la plus simple a deux emplacements - une clause et un bloc. leifmonad est généralement construit pour évaluer le résultat de la clause, et si ce n'est pas faux, évaluer le bloc. De nombreux développeurs ne sont pas initiés aux monades lorsqu'ils apprennent «si», et il n'est tout simplement pas nécessaire de comprendre les monades pour écrire une logique efficace.

Les monades peuvent devenir plus compliquées, de la même manière que les structures de données peuvent devenir plus compliquées, mais il existe de nombreuses grandes catégories de monades qui peuvent avoir une sémantique similaire, mais des implémentations et une syntaxe différentes.

Bien entendu, de la même manière que les structures de données peuvent être itérées sur, ou traversées, des monades peuvent être évaluées.

Les compilateurs peuvent ou non prendre en charge les monades définies par l'utilisateur. Haskell le fait certainement. Ioke a des capacités similaires, bien que le terme monade ne soit pas utilisé dans la langue.

La source
Translate

Mon tutoriel Monad préféré:

http://www.haskell.org/haskellwiki/All_About_Monads

(sur 170 000 visites sur une recherche Google pour "tutoriel monade"!)

@Stu: Le but des monades est de vous permettre d'ajouter (généralement) une sémantique séquentielle à du code autrement pur; vous pouvez même composer des monades (en utilisant des transformateurs Monad) et obtenir une sémantique combinée plus intéressante et plus compliquée, comme l'analyse avec gestion des erreurs, l'état partagé et la journalisation, par exemple. Tout cela est possible en code pur, les monades vous permettent simplement de l'abstraire et de le réutiliser dans des bibliothèques modulaires (toujours bonnes en programmation), ainsi que de fournir une syntaxe pratique pour le rendre impératif.

Haskell a déjà une surcharge d'opérateurs [1]: il utilise les classes de types de la même manière que l'on pourrait utiliser les interfaces en Java ou C # mais Haskell autorise également les jetons non alphanumériques comme + && et> comme identificateurs d'infixe. Ce n'est qu'une surcharge d'opérateur dans votre façon de voir si vous voulez dire "surcharger le point-virgule" [2]. Cela ressemble à de la magie noire et demander des ennuis pour "surcharger le point-virgule" (image des hackers Perl entreprenants ayant vent de cette idée) mais le fait est que sans monadesil n'y a pas de point-virgule, car le code purement fonctionnel ne nécessite ni ne permet un séquençage explicite.

Tout cela semble beaucoup plus compliqué que nécessaire. L'article de sigfpe est plutôt cool mais utilise Haskell pour l'expliquer, ce qui échoue en quelque sorte à résoudre le problème de la poule et de l'œuf de comprendre Haskell pour grok Monads et comprendre Monads pour grok Haskell.

[1] Il s'agit d'un problème distinct des monades, mais les monades utilisent la fonction de surcharge des opérateurs de Haskell.

[2] C'est aussi une simplification excessive puisque l'opérateur pour chaîner les actions monadiques est >> = (prononcé "bind") mais il existe un sucre syntaxique ("do") qui vous permet d'utiliser des accolades et des points-virgules et / ou des indentations et des retours à la ligne.

La source
Camille Lee
Translate

J'ai pensé aux Monades d'une manière différente, ces derniers temps. J'ai pensé à eux comme à l'abstraitordre d'exécutiond'une manière mathématique, ce qui rend possible de nouveaux types de polymorphisme.

Si vous utilisez un langage impératif et que vous écrivez certaines expressions dans l'ordre, le code s'exécute TOUJOURS exactement dans cet ordre.

Et dans le cas simple, lorsque vous utilisez une monade, vous ressentez la même chose: vous définissez une liste d'expressions qui se produisent dans l'ordre. Sauf que, selon la monade que vous utilisez, votre code peut s'exécuter dans l'ordre (comme dans la monade IO), en parallèle sur plusieurs éléments à la fois (comme dans la monade List), il peut s'arrêter à mi-chemin (comme dans la monade Maybe) , il peut faire une pause à mi-chemin pour être repris plus tard (comme dans une monade de reprise), il peut revenir en arrière et recommencer depuis le début (comme dans une monade de transaction), ou il peut revenir en arrière à mi-chemin pour essayer d'autres options (comme dans une monade Logic) .

Et comme les monades sont polymorphes, il est possible d'exécuter le même code dans différentes monades, en fonction de vos besoins.

De plus, dans certains cas, il est possible de combiner des monades ensemble (avec des transformateurs monades) pour obtenir plusieurs fonctionnalités en même temps.

La source
Translate

Je suis encore nouveau dans les monades, mais je pensais partager un lien que j'ai trouvé qui me faisait vraiment plaisir à lire (AVEC DES PHOTOS !!):http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/(pas d'affiliation)

Fondamentalement, le concept chaleureux et flou que j'ai obtenu de l'article était le concept que les monades sont essentiellement des adaptateurs qui permettent à des fonctions disparates de fonctionner de manière composable, c'est-à-dire être capable d'enchaîner plusieurs fonctions et de les mélanger et de les assortir sans se soucier d'un retour incohérent. types et autres. La fonction BIND est donc chargée de conserver les pommes avec des pommes et les oranges avec des oranges lorsque nous essayons de fabriquer ces adaptateurs. Et la fonction LIFT est chargée de prendre les fonctions de «niveau inférieur» et de les «mettre à jour» pour qu'elles fonctionnent avec les fonctions BIND et soient également composables.

J'espère avoir bien compris et, plus important encore, j'espère que l'article a une vision valable des monades. Si rien d'autre, cet article a contribué à aiguiser mon appétit pour en savoir plus sur les monades.

La source
Elliot Lee
Translate

En plus des excellentes réponses ci-dessus, permettez-moi de vous proposer un lien vers l'article suivant (par Patrick Thomson) qui explique les monades en reliant le concept à la bibliothèque JavaScriptjQuery(et sa façon d'utiliser le "chaînage de méthodes" pour manipuler le DOM):jQuery est une monade

ledocumentation jQuerylui-même ne fait pas référence au terme «monade» mais parle du «modèle de constructeur» qui est probablement plus familier. Cela ne change rien au fait que vous avez une monade appropriée là-bas peut-être sans même vous en rendre compte.

La source
Esther Lee
Translate

Les monades ne sont pas des métaphores, mais une abstraction pratiquement utile émergeant d'un schéma commun, comme l'explique Daniel Spiewak.

La source
Duncan Lee
Translate

Une monade est un moyen de combiner des calculs qui partagent un contexte commun. C'est comme construire un réseau de tuyaux. Lors de la construction du réseau, aucune donnée ne le traverse. Mais quand j'ai fini d'assembler tous les bits avec 'bind' et 'return' alors j'invoque quelque chose commerunMyMonad monad dataet les données circulent dans les tuyaux.

La source
Boris Lee
Translate

En pratique, monad est une implémentation personnalisée de l'opérateur de composition de fonction qui prend en charge les effets secondaires et les valeurs d'entrée et de retour incompatibles (pour le chaînage).

La source
Max Lee
Translate

Si j'ai bien compris, IEnumerable est dérivé des monades. Je me demande si cela pourrait être un angle d'approche intéressant pour ceux d'entre nous du monde C #?

Pour ce que ça vaut, voici quelques liens vers des tutoriels qui m'ont aidé (et non, je n'ai toujours pas compris ce que sont les monades).

La source
Clarence Lee
Translate

Les deux choses qui m'ont le plus aidé lors de mon apprentissage sont:

Chapitre 8, «Functional Parsers», extrait du livre de Graham HuttonProgrammation dans Haskell. Cela ne mentionne pas du tout les monades, en fait, mais si vous pouvez parcourir le chapitre et vraiment tout comprendre, en particulier comment une séquence d'opérations de liaison est évaluée, vous comprendrez les rouages des monades. Attendez-vous à ce que cela prenne plusieurs essais.

Le tutorielTout sur les monades. Cela donne plusieurs bons exemples de leur utilisation, et je dois dire que l'analogie dans Appendex a fonctionné pour moi.

La source
Deborah Lee
Translate

Monoid semble être quelque chose qui garantit que toutes les opérations définies sur un Monoid et un type pris en charge renverront toujours un type pris en charge dans le Monoid. Par exemple, n'importe quel nombre + n'importe quel nombre = un nombre, aucune erreur.

Alors que la division accepte deux fractionnaires, et renvoie une fraction, qui définit la division par zéro comme Infinity dans haskell somewhy (qui se trouve être une fraction de quelque part) ...

Dans tous les cas, il semble que les monades ne soient qu'un moyen de s'assurer que votre chaîne d'opérations se comporte de manière prévisible, et une fonction qui prétend être Num -> Num, composée avec une autre fonction de Num-> Num appelée avec x ne le fait pas dites, tirez les missiles.

D'un autre côté, si nous avons une fonction qui tire les missiles, nous pouvons la composer avec d'autres fonctions qui tirent également les missiles, car notre intention est claire - nous voulons tirer les missiles - mais cela n'essaiera pas. imprimer "Hello World" pour une raison étrange.

Dans Haskell, main est de type IO (), ou IO [()], la distiction est étrange et je ne vais pas en discuter mais voici ce que je pense qu'il se passe:

Si j'ai main, je veux qu'il fasse une chaîne d'actions, la raison pour laquelle je lance le programme est de produire un effet - généralement via IO. Ainsi, je peux enchaîner les opérations d'E / S ensemble dans le but de - faire des E / S, rien d'autre.

Si j'essaie de faire quelque chose qui ne "retourne pas IO", le programme se plaindra que la chaîne ne coule pas, ou fondamentalement "Comment cela se rapporte à ce que nous essayons de faire - une action IO", cela semble forcer le programmeur à garder le fil de ses pensées, sans s'égarer et penser à tirer les missiles, tout en créant des algorithmes de tri - qui ne coulent pas.

Fondamentalement, les Monades semblent être une astuce pour le compilateur: "hé, vous connaissez cette fonction qui renvoie un nombre ici, cela ne fonctionne pas toujours, cela peut parfois produire un nombre, et parfois rien du tout, gardez simplement cela dans esprit". Sachant cela, si vous essayez d'affirmer une action monadique, l'action monadique peut agir comme une exception à la compilation en disant "hé, ce n'est pas réellement un nombre, cela PEUT être un nombre, mais vous ne pouvez pas le supposer, faites quelque chose pour s'assurer que le débit est acceptable. " ce qui empêche un comportement imprévisible du programme - dans une bonne mesure.

Il semble que les monades ne concernent ni la pureté, ni le contrôle, mais le maintien d'une identité d'une catégorie sur laquelle tout comportement est prévisible et défini, ou ne se compile pas. Vous ne pouvez rien faire lorsque vous êtes censé faire quelque chose, et vous ne pouvez pas faire quelque chose si vous êtes censé ne rien faire (visible).

La principale raison à laquelle je pourrais penser pour les Monades est - allez voir le code procédural / POO, et vous remarquerez que vous ne savez pas où le programme commence, ni se termine, tout ce que vous voyez est beaucoup de sauts et beaucoup de maths , magie et missiles. Vous ne serez pas en mesure de le maintenir, et si vous le pouvez, vous passerez beaucoup de temps à réfléchir à tout le programme avant de pouvoir en comprendre une partie, car la modularité dans ce contexte est basée sur des "sections" interdépendantes du code, où le code est optimisé pour être aussi lié que possible pour une promesse d'efficacité / d'interrelation. Les monades sont très concrètes et bien définies par définition, et garantissent que le flux du programme est possible à analyser, et isolent des parties difficiles à analyser - car elles sont elles-mêmes des monades. Une monade semble être une "unité compréhensible qui est prévisible après sa pleine compréhension" - Si vous comprenez la monade "Peut-être", il n'y a aucun moyen de faire autre chose que d'être "Peut-être", ce qui semble trivial, mais dans la plupart des cas non monadique code, une simple fonction "helloworld" peut tirer les missiles, ne rien faire, ou détruire l'univers ou même déformer le temps - nous n'avons aucune idée ni aucune garantie que C'EST CE QUE C'EST. Une monade GARANTIT QUE C'EST CE QUE C'EST. ce qui est très puissant.

Toutes les choses dans le «monde réel» semblent être des monades, en ce sens qu'elles sont liées par des lois observables définies empêchant la confusion. Cela ne veut pas dire que nous devons imiter toutes les opérations de cet objet pour créer des classes, à la place, nous pouvons simplement dire "un carré est un carré", rien qu'un carré, pas même un rectangle ni un cercle, et "un carré a une aire de la longueur d'une de ses dimensions existantes multipliée par elle-même. Peu importe le carré que vous avez, s'il s'agit d'un carré dans un espace 2D, sa superficie ne peut absolument pas être autre chose que sa longueur au carré, c'est presque trivial à prouver. C'est très puissant car nous n'avons pas besoin de faire des affirmations pour nous assurer que notre monde est tel qu'il est, nous utilisons simplement les implications de la réalité pour empêcher nos programmes de dérailler.

Je suis à peu près sûr d'avoir tort, mais je pense que cela pourrait aider quelqu'un là-bas, alors j'espère que cela aide quelqu'un.

La source
Translate

Dans le contexte de Scala, vous trouverez ce qui suit comme la définition la plus simple. Fondamentalement, flatMap (ou bind) est «associatif» et il existe une identité.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

Par exemple

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

REMARQUEÀ proprement parler, la définition d'unMonade en programmation fonctionnellen'est pas la même que la définition d'unMonade dans la théorie des catégories, qui est défini en tours demapetflatten. Bien qu'ils soient en quelque sorte équivalents sous certains mappages. Cette présentation est très bonne:http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

La source
Translate

Cette réponse commence par un exemple motivant, fonctionne à travers l'exemple, dérive un exemple de monade et définit formellement «monade».

Considérez ces trois fonctions dans le pseudocode:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

fprend une paire ordonnée de la forme<x, messages>et renvoie une paire ordonnée. Il laisse le premier élément intact et ajoute"called f. "au deuxième élément. Même avecg.

Vous pouvez composer ces fonctions et obtenir votre valeur d'origine, ainsi qu'une chaîne qui montre dans quel ordre les fonctions ont été appelées:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Vous n'aimez pas le fait quefetgsont responsables de l'ajout de leurs propres messages de journal aux informations de journalisation précédentes. (Imaginez juste pour des raisons d'argumentation qu'au lieu d'ajouter des chaînes,fetgdoit exécuter une logique compliquée sur le deuxième élément de la paire. Ce serait pénible de répéter cette logique compliquée dans deux - ou plus - fonctions différentes.)

Vous préférez écrire des fonctions plus simples:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Mais regardez ce qui se passe lorsque vous les composez:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

Le problème est quequi passeune paire dans une fonction ne vous donne pas ce que vous voulez. Mais et si tu pouvaisalimentationune paire dans une fonction:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Lisfeed(f, m)comme "fluxmdansf". Àalimentationune paire<x, messages>en fonctionfest depasser xdansf, avoir<y, message>hors def, et retour<y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Remarquez ce qui se passe lorsque vous faites trois choses avec vos fonctions:

Premièrement: si vous enveloppez une valeur, puisalimentationla paire résultante en une fonction:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

C'est la même chose quequi passela valeur dans la fonction.

Deuxièmement: si vous introduisez une paire danswrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Cela ne change pas la paire.

Troisièmement: si vous définissez une fonction qui prendxet alimenteg(x)dansf:

h(x) := feed(f, g(x))

et nourrissez-y une paire:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

C'est la même chose que de nourrir la paire dansget nourrir la paire résultante dansf.

Vous avez presque une monade. Il vous suffit maintenant de connaître les types de données de votre programme.

Quel type de valeur est<x, "called f. ">? Eh bien, cela dépend du type de valeurxest. Sixest de typet, alors votre paire est une valeur de type "paire detand string ". Appelez ce typeM t.

Mest un constructeur de type:Mseul ne fait pas référence à un type, maisM _fait référence à un type une fois que vous remplissez le vide avec un type. UnM intest une paire d'un int et d'une chaîne. UnM stringest une paire d'une chaîne et d'une chaîne. Etc.

Félicitations, vous avez créé une monade!

Formellement, votre monade est le tuple<M, feed, wrap>.

Une monade est un tuple<M, feed, wrap>où:

  • Mest un constructeur de type.
  • feedprend une (fonction qui prend untet renvoie unM u) Et unM tet renvoie unM u.
  • wrapprend unvet renvoie unM v.

t, u, etvsont trois types quelconques qui peuvent être identiques ou non. Une monade satisfait les trois propriétés que vous avez prouvées pour votre monade spécifique:

  • Alimentationun enveloppétdans une fonction équivaut àqui passele déballétdans la fonction.

    Officiellement:feed(f, wrap(x)) = f(x)

  • Nourrir unM tdanswrapne fait rien auM t.

    Officiellement:feed(wrap, m) = m

  • Nourrir unM t(appelerm) en une fonction qui

    • passe letdansg
    • obtient unM u(appelern) deg
    • alimentendansf

    est le même que

    • alimentationmdansg
    • obtenirndeg
    • alimentationndansf

    Officiellement:feed(h, m) = feed(f, feed(g, m))h(x) := feed(f, g(x))

Typiquement,feedest appelébind(ALIAS>>=à Haskell) etwrapest appeléreturn.

La source
Translate

Je vais essayer d'expliquerMonaddans le contexte de Haskell.

Dans la programmation fonctionnelle, la composition des fonctions est importante. Il permet à notre programme de se composer de petites fonctions faciles à lire.

Disons que nous avons deux fonctions:g :: Int -> Stringetf :: String -> Bool.

Nous pouvons faire(f . g) x, qui est exactement la même chose quef (g x), oùxest unIntvaleur.

Lors de la composition / de l'application du résultat d'une fonction à une autre, il est important de faire correspondre les types. Dans le cas ci-dessus, le type du résultat renvoyé pargdoit être le même que le type accepté parf.

Mais parfois, les valeurs sont dans des contextes, ce qui rend un peu moins facile l'alignement des types. (Avoir des valeurs dans des contextes est très utile. Par exemple, leMaybe Inttype représente unIntvaleur qui peut ne pas être là, leIO Stringtype représente unStringvaleur qui est là à la suite de certains effets secondaires.)

Disons que nous avons maintenantg1 :: Int -> Maybe Stringetf1 :: String -> Maybe Bool. g1etf1sont très similaires àgetfrespectivement.

On ne peut pas faire(f1 . g1) xouf1 (g1 x), oùxest unIntvaleur. Le type du résultat renvoyé parg1n'est pas quoif1attend.

Nous pourrions composerfetgavec le.opérateur, mais maintenant nous ne pouvons pas composerf1etg1avec.. Le problème est que nous ne pouvons pas passer directement une valeur dans un contexte à une fonction qui attend une valeur qui n'est pas dans un contexte.

Ne serait-ce pas bien si nous introduisions un opérateur pour composerg1etf1, de sorte que nous puissions écrire(f1 OPERATOR g1) x? g1renvoie une valeur dans un contexte. La valeur sera prise hors contexte et appliquée àf1. Et oui, nous avons un tel opérateur. Ses<=<.

Nous avons également le>>=opérateur qui fait exactement la même chose pour nous, mais dans une syntaxe légèrement différente.

Nous écrivons:g1 x >>= f1. g1 xest unMaybe Intvaleur. le>>=l'opérateur aide à prendre celaIntla valeur hors du contexte "peut-être pas là" et l'appliquer àf1. Le résultat def1, qui est unMaybe Bool, sera le résultat de l'ensemble>>=opération.

Et enfin, pourquoiMonadutile? CarMonadest la classe de type qui définit le>>=opérateur, à peu près le même que leEqclasse de type qui définit le==et/=les opérateurs.

Pour conclure, leMonadla classe de type définit le>>=opérateur qui nous permet de passer des valeurs dans un contexte (nous appelons ces valeurs monadiques) à des fonctions qui n'attendent pas de valeurs dans un contexte. Le contexte sera pris en compte.

S'il y a une chose à retenir ici, c'est queMonads autorise la composition de fonctions qui implique des valeurs dans des contextes.

La source
Translate

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Prologue

L'opérateur de l'application$de fonctions

forall a b. a -> b

est défini canoniquement

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

en termes d'application de fonction primitive Haskellf x (infixl 10).

Composition.est défini en termes de$comme

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

et satisfait les équivalencesforall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.est associatif, etidest son identité droite et gauche.

Le triple Kleisli

En programmation, une monade est un constructeur de type foncteur avec une instance de la classe de type monade. Il existe plusieurs variantes équivalentes de définition et de mise en œuvre, chacune portant des intuitions légèrement différentes sur l'abstraction de la monade.

Un foncteur est un constructeur de typefdu genre* -> *avec une instance de la classe de type functor.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

En plus de suivre le protocole de type appliqué statiquement, les instances de la classe de type foncteur doivent obéir à l'algébriquelois des foncteurs forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Functorcalculsavoir le type

forall f t. Functor f => f t

Un calculc rConsiste enrésultats rdansle contexte c.

Fonctions monadiques unaires ouFlèches Kleisliavoir le type

forall m a b. Functor m => a -> m b

Les flèches de Kleisi sont des fonctions qui prennent un argumentaet renvoyer un calcul monadiquem b.

Les monades sont définies canoniquement en termes deKleisli triple forall m. Functor m =>

(m, return, (=<<))

implémenté comme classe de type

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

leIdentité Kleisli returnest une flèche de Kleisli qui favorise une valeurtdans un contexte monadiquem. ExtensionouApplication Kleisli =<<applique une flèche Kleislia -> m baux résultats d'un calculm a.

Composition de Kleisli <=<est défini en termes d'extension comme

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=<compose deux flèches Kleisli, appliquant la flèche gauche aux résultats de l'application de la flèche droite.

Les instances de la classe de type monade doivent obéir aulois de la monade, plus élégamment énoncée en termes de composition de Kleisli:forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<est associatif, etreturnest son identité droite et gauche.

Identité

Le type d'identité

type Id t = t

est la fonction d'identité sur les types

Id :: * -> *

Interprété comme un foncteur,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

Dans Haskell canonique, la monade d'identité est définie

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Option

Un type d'option

data Maybe t = Nothing | Just t

encode le calculMaybe tqui ne donne pas nécessairement un résultatt, calcul qui peut «échouer». L'option monade est définie

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe best appliqué à un résultat uniquement siMaybe adonne un résultat.

newtype Nat = Nat Int

Les nombres naturels peuvent être codés sous forme d'entiers supérieurs ou égaux à zéro.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Les nombres naturels ne sont pas fermés par soustraction.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

L'option monade couvre une forme de base de gestion des exceptions.

(-? 20) <=< toNat :: Int -> Maybe Nat

liste

La monade de liste, sur le type de liste

data [] t = [] | t : [t]

infixr 5 :

et son opération monoïde additive "append"

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

encodenon linéairecalcul[t]donnant une quantité naturelle0, 1, ...de résultatst.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

Extension=<<concatène++toutes les listes[b]résultant d'applicationsf xd'une flèche Kleislia -> [b]aux éléments de[a]en une seule liste de résultats[b].

Soit les diviseurs propres d'un entier positifnêtre

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

puis

forall n.  let { f = f <=< divisors } in f n   =   []

En définissant la classe de type monade, au lieu de l'extension=<<, le standard Haskell utilise son flip, lelieropérateur>>=.

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

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

Par souci de simplicité, cette explication utilise la hiérarchie des classes de types

class              Functor f
class Functor m => Monad m

Dans Haskell, la hiérarchie standard actuelle est

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

car non seulement chaque monade est un foncteur, mais chaque applicatif est un foncteur et chaque monade est aussi un applicatif.

Utilisation de la monade de liste, le pseudocode impératif

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

se traduit approximativement parbloquer,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

l'équivalentcompréhension de la monade,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

et l'expression

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

La notation et la compréhension des monades sont du sucre syntaxique pour les expressions de liaison imbriquées. L'opérateur de liaison est utilisé pour la liaison de nom local des résultats monadiques.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

La fonction de garde est définie

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

où letype d'unitéou "tuple vide"

data () = ()

Monades additivesce soutienchoixetéchecpeut être abstrait en utilisant une classe de type

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

failet<|>former un monoïdeforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

etfailest l'élément zéro absorbant / annihilant des monades additives

_ =<< fail  =  fail

Si dans

guard (even p) >> return p

even pest vrai, alors le garde produit[()], et, par la définition de>>, la fonction constante locale

\ _ -> return p

est appliqué au résultat(). Si faux, alors le garde produit la liste de la monadefail([]), qui ne donne aucun résultat pour l'application d'une flèche de Kleisli>>à, donc çapest ignoré.

Etat

Tristement célèbre, les monades sont utilisées pour coder le calcul avec état.

A processeur d'étatest une fonction

forall st t. st -> (t, st)

qui transite un étatstet donne un résultatt. leEtat stpeut être n'importe quoi. Rien, drapeau, compte, tableau, poignée, machine, monde.

Le type de processeurs d'état est généralement appelé

type State st t = st -> (t, st)

La monade de processeur d'état est la sorte* -> *foncteurState st. Les flèches Kleisli de la monade du processeur d'état sont des fonctions

forall st a b. a -> (State st) b

Dans Haskell canonique, la version paresseuse de la monade de processeur d'état est définie

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Un processeur d'état est exécuté en fournissant un état initial:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

L'accès à l'état est fourni par des primitivesgetetput, méthodes d'abstraction suravec étatmonades:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st | m -> st where
   get :: m st
   put :: st -> m ()

m -> stdéclare undépendance fonctionnelledu type d'étatstsur la monadem; qu'unState t, par exemple, déterminera le type d'état à êtretuniquement.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

avec le type d'unité utilisé de manière analoguevoiden C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

getsest souvent utilisé avec les accesseurs de champ d'enregistrement.

L'équivalent de la monade d'état du threading variable

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

s0 :: Int, est le tout aussi référentiellement transparent, mais infiniment plus élégant et pratique

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)est un calcul de typeState Int (), sauf pour soneffetéquivalent àreturn ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

La loi de monade d'associativité peut être écrite en termes de>>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

or

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

Comme dans la programmation orientée expression (par exemple Rust), la dernière instruction d'un bloc représente son rendement. L'opérateur de liaison est parfois appelé «point-virgule programmable».

Les primitives de structure de contrôle d'itération issues de la programmation impérative structurée sont émulées de manière monadique

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Entrée sortie

data World

La monade du processeur d'état du monde d'E / S est une réconciliation de Haskell pur et du monde réel, de sémantique opérationnelle dénotative fonctionnelle et impérative. Un analogue proche de la mise en œuvre stricte actuelle:

type IO t = World -> (t, World)

L'interaction est facilitée par des primitives impures

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

L'impureté du code qui utiliseIOprimitives est en permanence protocolisé par le système de types. Parce que la pureté est géniale, ce qui se passe dansIO, reste dansIO.

unsafePerformIO :: IO t -> t

Ou, du moins, devrait.

La signature de type d'un programme Haskell

main :: IO ()
main = putStrLn "Hello, World!"

s'étend à

World -> ((), World)

Une fonction qui transforme un monde.

Épilogue

La catégorie dont les objets sont des types Haskell et dont les morphismes sont des fonctions entre les types Haskell est, «rapide et lâche», la catégorieHask.

Un foncteurTest une cartographie d'une catégorieCà une catégorieD; pour chaque objet dansCun objet dansD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

et pour chaque morphisme dansCun morphisme dansD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

X, Ysont des objets dansC. HomC(X, Y)est leclasse d'homomorphismede tous les morphismesX -> YdansC. Le foncteur doit préserver l'identité et la composition du morphisme, la «structure» duC, dansD.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

leCatégorie Kleislid'une catégorieCest donné par un triple de Kleisli

<T, eta, _*>

d'un endofoncteur

T : C -> C

(f), un morphisme identitaireeta (return) et un opérateur d'extension* (=<<).

Chaque morphisme de KleisliHask

      f :  X -> T(Y)
      f :: a -> m b

par l'opérateur d'extension

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

reçoit un morphisme dansHaskCatégorie Kleisli

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Composition dans la catégorie Kleisli.Test donné en termes d'extension

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

et satisfait leaxiomes de catégorie

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

qui, en appliquant les transformations d'équivalence

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

en termes d'extension sont donnés canoniquement

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Les monades peuvent également être définies en termes non pas d'extension kleislienne, mais d'une transformation naturellemu, dans la programmation appeléejoin. Une monade est définie en termes demucomme un triple sur une catégorieC, d'un endofoncteur

     T :  C -> C
     f :: * -> *

et deux transformations naturelles

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

satisfaire les équivalences

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

La classe de type monade est alors définie

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

Le canoniquemuimplémentation de l'option monade:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

leconcatfonction

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

est lejoinde la liste monade.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Implémentations dejoinpeut être traduit à partir du formulaire d'extension en utilisant l'équivalence

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

La traduction inverse demula forme d'extension est donnée par

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Mais pourquoi une théorie aussi abstraite devrait-elle être utile pour la programmation?

La réponse est simple: en tant qu'informaticiens, nousabstraction de valeur! Lorsque nous concevons l'interface avec un composant logiciel, nousvouloirpour en révéler le moins possible sur la mise en œuvre. Nous voulons pouvoir remplacer l'implémentation par de nombreuses alternatives, de nombreuses autres «instances» du même «concept». Lorsque nous concevons une interface générique pour de nombreuses bibliothèques de programmes, il est encore plus important que l'interface que nous choisissons ait une variété d'implémentations. C'est la généralité du concept de monade que nous apprécions tellement, c'estcarla théorie des catégories est si abstraite que ses concepts sont si utiles pour la programmation.

Il n'est donc guère surprenant que la généralisation des monades que nous présentons ci-dessous ait également un lien étroit avec la théorie des catégories. Mais nous soulignons que notre but est très pratique: ce n'est pas de «mettre en œuvre la théorie des catégories», c'est de trouver une manière plus générale de structurer les bibliothèques de combinateurs. C'est simplement notre chance que les mathématiciens aient déjà fait une grande partie du travail pour nous!

deGénéraliser des monades en flèchespar John Hughes

La source
Yale Lee
Translate

Ce dont le monde a besoin, c'est d'un autre article de blog sur les monades, mais je pense que cela est utile pour identifier les monades existantes dans la nature.

Sierpinski triangle

Ce qui précède est une fractale appelée triangle de Sierpinski, la seule fractale que je me souvienne de dessiner. Les fractales sont une structure auto-similaire comme le triangle ci-dessus, dans lequel les parties sont similaires au tout (dans ce cas, exactement la moitié de l'échelle en tant que triangle parent).

Les monades sont des fractales. Étant donné une structure de données monadique, ses valeurs peuvent être composées pour former une autre valeur de la structure de données. C'est pourquoi il est utile à la programmation, et c'est pourquoi cela se produit dans de nombreuses situations.

La source
Translate

http://code.google.com/p/monad-tutorial/est un travail en cours pour répondre exactement à cette question.

La source
Translate

Une monade est une chose utilisée pour encapsuler des objets dont l'état change. Il est le plus souvent rencontré dans des langages qui autrement ne vous permettent pas d'avoir un état modifiable (par exemple Haskell).

Un exemple serait pour les E / S de fichier.

Vous pourriez utiliser une monade pour les E / S de fichier afin d'isoler la nature changeante de l'état au seul code qui a utilisé la Monad. Le code à l'intérieur de la Monad peut effectivement ignorer l'état changeant du monde en dehors de la Monade - cela facilite beaucoup la réflexion sur l'effet global de votre programme.

La source
Translate

Laissez le ci-dessous "{| a |m}"représentent des données monadiques. Un type de données qui annonce una:

        (I got an a!)
          /        
    {| a |m}

Fonction,f, sait créer une monade, si seulement elle avait una:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Ici, nous voyons la fonction,f, essaie d'évaluer une monade mais se fait réprimander.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtion,f, trouve un moyen d'extraire leaen utilisant>>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Peu de chosesfsais, la monade et>>=sont en collusion.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

Mais de quoi parlent-ils réellement? Eh bien, cela dépend de la monade. Parler uniquement dans l'abstrait a une utilité limitée; vous devez avoir une certaine expérience avec des monades particulières pour étoffer la compréhension.

Par exemple, le type de données Maybe

 data Maybe a = Nothing | Just a

a une instance de monade qui agira comme suit ...

Où, si le cas estJust a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Mais pour le cas deNothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Ainsi, la monade Maybe laisse un calcul continuer s'il contient réellement leail annonce, mais abandonne le calcul si ce n'est pas le cas. Le résultat, cependant, est toujours un élément de données monadiques, mais pas la sortie def. Pour cette raison, la monade Maybe est utilisée pour représenter le contexte de l'échec.

Différentes monades se comportent différemment. Les listes sont d'autres types de données avec des instances monadiques. Ils se comportent comme suit:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

Dans ce cas, la fonction savait comment créer une liste à partir de son entrée, mais ne savait pas quoi faire avec des entrées supplémentaires et des listes supplémentaires. La liaison>>=, aidéfen combinant les multiples sorties. J'inclus cet exemple pour montrer que pendant>>=est responsable de l'extractiona, il a également accès à l'éventuelle sortie liée def. En effet, il n'en extraira jamaisaà moins qu'il ne sache que la sortie éventuelle a le même type de contexte.

Il existe d'autres monades utilisées pour représenter différents contextes. Voici quelques caractérisations de quelques autres. leIOla monade n'a pas vraiment dea, mais il connaît un mec et l'obtiendraapour vous. leState stla monade a une cachette secrète destqu'il passera àfsous la table, même sifje viens de demander una. leReader rmonade est similaire àState st, bien que cela ne laissefRegarderr.

Le point dans tout cela est que tout type de données qui se déclare être une monade déclare une sorte de contexte autour de l'extraction d'une valeur de la monade. Le gros gain de tout ça? Eh bien, il est assez facile de présenter un calcul avec une sorte de contexte. Cependant, cela peut devenir compliqué lors de l'enchaînement de plusieurs calculs chargés de contexte. Les opérations de monade s'occupent de résoudre les interactions de contexte pour que le programmeur n'ait pas à le faire.

Notez que l'utilisation du>>=atténue le gâchis en supprimant une partie de l'autonomief. Autrement dit, dans le cas ci-dessus deNothingpar exemple,fne décide plus quoi faire en cas deNothing; c'est encodé en>>=. C'est le compromis. Si c'était nécessaire pourfpour décider quoi faire en cas deNothing, puisfaurait dû être une fonction deMaybe aàMaybe b. Dans ce cas,Maybeêtre une monade n'a pas d'importance.

Notez cependant que parfois un type de données n'exporte pas ses constructeurs (en vous regardant IO), et si nous voulons travailler avec la valeur publiée, nous n'avons guère d'autre choix que de travailler avec son interface monadique.

La source