Qu'est-ce que les monades ont de si spécial?

une monade est une structure mathématique qui est largement utilisée dans la programmation fonctionnelle (pure), essentiellement Haskell. Cependant, il existe de nombreuses autres structures mathématiques disponibles, comme par exemple des fonctions applicatives, monades fortes, ou des monoïdes. Certains ont plus spécifique, certains sont plus générique . Pourtant, les monades sont beaucoup plus populaires. Pourquoi est-ce?

une explication que j'ai trouvé, c'est qu'ils sont un point doux entre la généralité et spécificité. Cela signifie que les monades capturent suffisamment d'hypothèses sur les données pour appliquer les algorithmes que nous utilisons habituellement et les données que nous avons habituellement remplit les lois monadiques.

une autre explication pourrait être que Haskell fournit la syntaxe pour les monades (do-notation), mais pas pour les autres structures, ce qui signifie que les programmeurs Haskell (et donc les chercheurs de programmation fonctionnelle) sont intuitivement attirés vers les monades, où une fonction plus générique ou spécifique (efficace) fonctionnerait comme bien.

25
demandé sur Community 2013-05-08 15:05:24

5 réponses

je soupçonne que l'attention disproportionnée accordée à cette classe de type particulière ( Monad ) par rapport aux nombreuses autres est principalement un hasard historique. Les gens associent souvent IO avec Monad , bien que les deux soient indépendamment des idées utiles ( comme le sont le renversement de liste et les bananes ). Parce que IO est magique (avec une implémentation mais sans dénotation) et Monad est souvent associé à IO , il est facile de tomber dans la pensée magique à propos de Monad .

(mis à part: on peut se demander si IO est même une monade. Faire la monade lois tenir? Qu'est-ce que les lois , même , signifient pour IO , c'est-à-dire qu'est-ce que l'égalité signifie? Notez le problématique association avec l'etat monade .)

28
répondu Conal 2017-04-12 07:31:17

si un type m :: * -> * a une instance Monad , vous obtenez Turing-composition complète des fonctions avec le type a -> m b . C'est une propriété incroyablement utile. Vous obtenez la capacité d'abstraire divers Turing-contrôle complet s'écoule loin de significations spécifiques. C'est un modèle de composition minimale qui supporte l'abstraction de tout flux de contrôle pour travailler avec des types qui le supportent.

comparez ceci à Applicative , par exemple. Là, vous obtenez seulement les modèles de composition avec une puissance de calcul équivalente à un automate push-down. Bien sûr, il est vrai que plus de types soutiennent la composition avec une puissance plus limitée. Et il est vrai que lorsque vous limitez la puissance disponible, vous pouvez faire des optimisations supplémentaires. Ces deux raisons expliquent l'existence et l'utilité de la classe Applicative . Mais les choses qui peuvent être des instances de Monad le sont généralement, de sorte que les utilisateurs du type peuvent effectuer les opérations les plus générales possibles avec le type.

Edit: Par demande populaire, voici quelques fonctions utilisant la classe Monad :

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM c x y = c >>= \z -> if z then x else y

whileM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
whileM p step x = ifM (p x) (step x >>= whileM p step) (return x)

(*&&) :: Monad m => m Bool -> m Bool -> m Bool
x *&& y = ifM x y (return False)

(*||) :: Monad m => m Bool -> m Bool -> m Bool
x *|| y = ifM x (return True) y

notM :: Monad m => m Bool -> m Bool
notM x = x >>= return . not

combinant ceux avec la syntaxe de do (ou l'opérateur raw >>= ) vous donne la liaison de nom, boucle indéfinie, et la logique booléenne complète. C'est un ensemble bien connu de primitifs suffisant pour donner L'intégralité de Turing. Notez comment toutes les fonctions ont été levées pour travailler sur des valeurs monadiques, plutôt que des valeurs simples. Tous les monadique les effets ne sont liés qu'en cas de nécessité - seuls les effets de la branche choisie de ifM sont liés dans leur valeur finale. Les deux *&& et *|| ignorent leur deuxième argument lorsque cela est possible. Et ainsi de suite..

maintenant, ces signatures de type peuvent ne pas impliquer des fonctions pour chaque opérande monadique, mais c'est juste une simplification cognitive. Il n'y aurait pas de différence sémantique, en ignorant les fonds, si tous les arguments et résultats hors fonction étaient changés en () -> m a . Il est tout simplement plus convivial pour les utilisateurs d'optimiser cet overhead cognitif.

voyons maintenant ce qu'il advient de ces fonctions avec l'interface Applicative .

ifA :: Applicative f => f Bool -> f a -> f a -> f a
ifA c x y = (\c' x' y' -> if c' then x' else y') <$> c <*> x <*> y

bien, euh. Il a reçu le même type de signature. Mais il y a déjà un gros problème ici. Les effets de x et de y sont liés dans la structure composée, quelle que soit la valeur sélectionnée.

whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <$> step x) (pure x)

bien, ok, cela semble comme si ce serait ok, sauf pour le fait que c'est une boucle infinie parce que ifA exécutera toujours les deux branches... Sauf que c'est pas encore pour tout de suite. pure x a le type f a . whileA p step <$> step x a le type f (f a) . Ce n'est même pas une boucle infinie. C'est une erreur de compilation. Essayez encore une fois..

whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <*> step x) (pure x)

Bien tirer. Ne même pas en arriver là. whileA p step a le type a -> f a . Si vous essayez de l'utiliser comme premier argument à <*> , il saisit l'instance Applicative pour le TOP type constructeur , qui est (->) , pas f . Ouais, ce n'est pas qui va travailler.

en fait, la seule fonction de mes exemples Monad qui fonctionnerait avec l'interface Applicative est notM . Cette fonction particulière fonctionne très bien avec seulement une interface Functor , en fait. Le reste? Ils échouent.

bien sûr il faut s'attendre à ce que vous puissiez écrire du code en utilisant l'interface Monad que vous ne pouvez pas utiliser avec l'interface Applicative . Il est strictement plus puissant, après tout. Mais ce qui est intéressant, c'est ce que vous perdez. Vous perdez la capacité de composer des fonctions qui changent les effets qu'ils ont basés sur leur entrée. C'est-à-dire que vous perdez la capacité d'écrire certains modèles de flux de contrôle qui composent des fonctions avec les types a -> f b .

Turing-complet de la composition est exactement ce qui rend l'interface Monad intéressante. S'il ne permettait pas une composition Turing-complete, il serait impossible pour vous, le programmeur, de composer ensemble des actions IO dans n'importe quel flux de contrôle particulier qui n'était pas bien préemballé pour vous. C'est le fait que vous pouvez utiliser les primitives Monad pour exprimer n'importe quel "débit de commande qui a fait du type IO un moyen faisable de gérer le problème IO en Haskell.

beaucoup plus de types que juste IO ont sémantiquement valide Monad interfaces. Et il se trouve que Haskell a les facilités linguistiques pour abstraire sur toute l'interface. En raison de ces facteurs, Monad est une classe précieuse pour fournir des instances pour, si possible. Ce faisant, vous accédez à toutes les fonctionnalités abstraites existantes prévues pour travailler avec les types monadiques, indépendamment de ce qu'est le type concret.

donc si les programmeurs Haskell semblent toujours se soucier des instances Monad pour un type, c'est parce que c'est l'instance la plus utile de manière générale qui peut être fournie.

15
répondu Carl 2013-05-17 20:48:28

tout d'abord, je pense qu'il n'est pas tout à fait vrai que les monades sont beaucoup plus populaires que toute autre chose; à la fois Functor et Monoid ont de nombreux exemples qui ne sont pas des monades. Mais ils sont tous les deux très spécifiques; Functor fournit la cartographie, la concaténation monoïde. Applicative est la classe que je peux penser qui est probablement sous-utilisé compte tenu de son pouvoir considérable, en grande partie parce qu'il est un ajout relativement récent à la langue.

Mais oui, les monades sont extrêmement populaires. Une partie de cela est la notation de do; beaucoup de monoïdes fournissent des instances de monades qui ajoutent simplement des valeurs à un accumulateur courant (essentiellement un écrivain implicite). La bibliothèque blaze-html en est un bon exemple. La raison, je pense, est la puissance du type de signature (>>=) :: Monad m => m a -> (a -> m b) -> m b . Alors que fmap et mappend sont utiles, ce qu'ils peuvent faire est assez étroitement limitée. lier, cependant, peut exprimer une grande variété de choses. Il est, bien sûr, canonisé dans le io monad, peut-être la meilleure approche fonctionnelle pure pour IO avant flux et FRP (et toujours utile à côté d'eux pour les tâches simples et la définition de composants). Mais il fournit également état implicite (Lecteur / Écrivain / ST), qui peut éviter certains passage de variable très fastidieuse. Les différentes monades d'état, en particulier, sont importantes parce qu'elles fournissent une garantie que l'état est fileté simple, permettant des structures mutables en code pur (non-IO) avant la fusion. Mais bind a quelques utilisations plus exotiques, telles que l'aplatissement des structures de données imbriquées( la liste et les monades de jeu), à la fois qui sont tout à fait utiles à leur place (et je les vois habituellement utilisés desugared, appelant liftM ou (>>=) explicitement, donc ce n'est pas une question de notation do). Ainsi, tandis que le Functor et le Monoid (et le Foldable un peu plus rare, Alternative, Traversable, et d'autres) fournissent une interface standardisée à une fonction assez simple, la liaison de Monad est considérablement plus de flexibilité.

en bref, je pense que toutes vos raisons ont un certain rôle; la popularité des monades est due à un combinaison de l'accident historique (notation do et la définition tardive de L'applicatif) et leur combinaison de la puissance et de la généralité (par rapport aux fonctions, monoïdes, et similaires) et de la compréhensibilité (par rapport aux flèches).

12
répondu isturdy 2013-05-08 15:26:57

Eh bien, permettez-moi d'abord d'expliquer ce que le rôle des monades est: les monades sont très puissantes, mais dans un certain sens: vous pouvez à peu près tout exprimer à l'aide d'une monade. Haskell en tant que langue n'a pas de boucles d'action, d'exceptions, de mutation, de goto, etc. Les monades peuvent être exprimées dans la langue (donc elles ne sont pas spéciales) et rendent toutes ces choses accessibles.

il y a un côté positif et un côté négatif à cela: il est positif que vous pouvez exprimer tous ceux les structures de contrôle que vous connaissez de la programmation impérative et un tas d'autres que vous ne connaissez pas. J'ai récemment développé un monad qui vous permet de revenir à un calcul quelque part au milieu avec un contexte légèrement modifié. De cette façon, vous pouvez exécuter un calcul, et si elle échoue, vous essayez juste encore un peu les valeurs réglées. En outre, les actions monadiques sont de première classe, et c'est ainsi que vous construisez des choses comme des boucles ou la gestion des exceptions. Alors que while est primitif en C à Haskell il est en fait juste une régulière fonction .

le côté négatif est que les monades vous donnent à peu près aucune garantie que ce soit. Ils sont si puissants que vous êtes autorisé à faire ce que vous voulez, tout simplement. En d'autres termes, tout comme vous le savez à partir des langues impératives, il peut être difficile de raisonner sur le code en le regardant simplement.

les abstractions plus générales sont plus générales dans le sens où elles permettent à certains concepts d'être exprimé que vous ne pouvez pas exprimer en monades. Mais c'est seulement une partie de l'histoire. Même pour les monades vous pouvez utiliser un style connu comme applicative style , dans lequel vous utilisez l'interface applicative pour composer votre programme de petites parties isolées. L'avantage de ceci est que vous pouvez raisonner sur le code en le regardant simplement et vous pouvez développer des composants sans avoir à prêter attention au reste de votre système.

6
répondu ertes 2013-05-08 11:35:16

monades sont spéciales en raison de do notation, qui vous permet d'écrire des programmes impératifs dans un langage fonctionnel. Monad est l'abstraction qui vous permet d'assembler des programmes impératifs à partir de composants plus petits et réutilisables (qui sont eux-mêmes des programmes impératifs). Monade transformateurs sont spéciaux parce qu'ils représentent l'amélioration d'un langage impératif avec de nouvelles fonctionnalités.

0
répondu Dan Burton 2013-05-17 04:47:32