Vous pourriez voir une monade donnée m
comme un ensemble/famille (ou royaume, domaine, etc.) de actions (pensez à une déclaration C). La monade m
définit le type d'effets (secondaires) que ses actions peuvent avoir :
- avec
[]
vous pouvez définir des actions qui peuvent bifurquer leurs exécutions dans différents "mondes parallèles indépendants" ;
- avec
Either Foo
vous pouvez définir des actions qui peuvent échouer avec des erreurs de type Foo
;
- avec
IO
vous pouvez définir des actions qui peuvent avoir des effets secondaires sur le "monde extérieur" (accéder à des fichiers, au réseau, lancer des processus, faire un GET HTTP ...) ;
- vous pouvez disposer d'une monade dont l'effet est le "hasard" (voir le package
MonadRandom
) ;
- vous pouvez définir une monade dont les actions peuvent effectuer un déplacement dans un jeu (disons les échecs, le go ) et recevoir un déplacement d'un adversaire, mais qui n'est pas capable d'écrire dans votre système de fichiers ou autre.
Résumé
Si m
est une monade, m a
est un action qui produit un résultat/sortie de type a
.
El >>
y >>=
Les opérateurs sont utilisés pour créer des actions plus complexes à partir d'actions plus simples :
-
a >> b
est une macro-action qui fait l'action a
et ensuite l'action b
;
-
a >> a
fait l'action a
et ensuite l'action a
encore ;
- avec
>>=
la deuxième action peut dépendre de la sortie de la première.
La signification exacte de ce qu'est un action est et ce que faire une action et ensuite une autre dépend de la monade : chaque monade définit un sous-langage impératif avec certaines caractéristiques/effets.
Séquençage simple ( >>
)
Disons que nous avons une monade donnée M
et quelques actions incrementCounter
, decrementCounter
, readCounter
:
instance M Monad where ...
-- Modify the counter and do not produce any result:
incrementCounter :: M ()
decrementCounter :: M ()
-- Get the current value of the counter
readCounter :: M Integer
Nous aimerions maintenant faire quelque chose d'intéressant avec ces actions. La première chose que nous voulons faire avec ces actions est de les séquencer. Comme en C, par exemple, nous aimerions pouvoir faire :
// This is C:
counter++;
counter++;
Nous définissons un "opérateur de séquençage" >>
. En utilisant cet opérateur, nous pouvons écrire :
incrementCounter >> incrementCounter
Quel est le type de "incrementCounter >> incrementCounter" ?
-
C'est une action composée de deux actions plus petites, comme en C, vous pouvez écrire des déclarations composées à partir de déclarations atomiques :
// This is a macro statement made of several statements
{
counter++;
counter++;
}
// and we can use it anywhere we may use a statement:
if (condition) {
counter++;
counter++;
}
-
il peut avoir le même type d'effets que ses sous-actions ;
-
il ne produit pas de sortie/résultat.
Nous aimerions donc incrementCounter >> incrementCounter
pour être de type M ()
une (macro-)action avec le même type d'effets possibles mais sans aucun résultat.
Plus généralement, étant donné deux actions :
action1 :: M a
action2 :: M b
nous définissons une a >> b
comme la macro-action qui est obtenue par en faisant (quoi que cela signifie dans notre domaine d'action) a
puis b
et produit en sortie le résultat de l'exécution de la seconde action. Le type de >>
est :
(>>) :: M a -> M b -> M b
ou plus généralement :
(>>) :: (Monad m) => m a -> m b -> m b
Nous pouvons définir des séquences d'actions plus importantes à partir d'actions plus simples :
action1 >> action2 >> action3 >> action4
Entrées et sorties ( >>=
)
Nous aimerions être en mesure d'incrémenter par autre chose que 1 à la fois :
incrementBy 5
Nous voulons fournir une entrée dans nos actions, pour ce faire, nous définissons une fonction incrementBy
prendre un Int
et de produire une action :
incrementBy :: Int -> M ()
Maintenant nous pouvons écrire des choses comme :
incrementCounter >> readCounter >> incrementBy 5
Mais nous n'avons aucun moyen d'alimenter la sortie de readCounter
en incrementBy
. Pour ce faire, une version légèrement plus puissante de notre opérateur de séquençage est nécessaire. Le site >>=
L'opérateur peut utiliser la sortie d'une action donnée comme entrée de l'action suivante. On peut écrire :
readCounter >>= incrementBy
Il s'agit d'une action qui exécute le readCounter
et alimente sa sortie dans le incrementBy
et ensuite exécuter l'action qui en résulte.
Le type de >>=
est :
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Un exemple (partiel)
Disons que j'ai un Prompt
monade qui ne peut qu'afficher des informations (texte) à l'utilisateur et demander des informations à l'utilisateur :
-- We don't have access to the internal structure of the Prompt monad
module Prompt (Prompt(), echo, prompt) where
-- Opaque
data Prompt a = ...
instance Monad Prompt where ...
-- Display a line to the CLI:
echo :: String -> Prompt ()
-- Ask a question to the user:
prompt :: String -> Prompt String
Essayons de définir un promptBoolean message
actions qui posent une question et produisent une valeur booléenne.
Nous utilisons l'invite (message ++ "[y/n]")
et alimenter sa sortie à une fonction f
:
-
f "y"
devrait être une action qui ne fait que produire True
comme sortie ;
-
f "n"
devrait être une action qui ne fait que produire False
comme sortie ;
-
toute autre action devrait redémarrer l'action (refaire l'action) ;
promptBoolean
ressemblerait à ceci :
-- Incomplete version, some bits are missing:
promptBoolean :: String -> M Boolean
promptBoolean message = prompt (message ++ "[y/n]") >>= f
where f result = if result == "y"
then ???? -- We need here an action which does nothing but produce `True` as output
else if result=="n"
then ???? -- We need here an action which does nothing but produce `False` as output
else echo "Input not recognised, try again." >> promptBoolean
Produire une valeur sans effet ( return
)
Afin de combler les lacunes de notre promptBoolean
nous avons besoin d'un moyen de représenter des actions fictives sans effet secondaire mais qui ne produisent qu'une valeur donnée :
-- "return 5" is an action which does nothing but outputs 5
return :: (Monad m) => a -> m a
et nous pouvons maintenant écrire promptBoolean
función:
promptBoolean :: String -> Prompt Boolean
promptBoolean message :: prompt (message ++ "[y/n]") >>= f
where f result = if result=="y"
then return True
else if result=="n"
then return False
else echo "Input not recognised, try again." >> promptBoolean message
En composant ces deux actions simples ( promptBoolean
, echo
) nous pouvons définir n'importe quel type de dialogue entre l'utilisateur et votre programme (les actions du programme sont déterministes car notre monade n'a pas d'"effet aléatoire").
promptInt :: String -> M Int
promptInt = ... -- similar
-- Classic "guess a number game/dialogue"
guess :: Int -> m()
guess n = promptInt "Guess:" m -> f
where f m = if m == n
then echo "Found"
else (if m > n
then echo "Too big"
then echo "Too small") >> guess n
Les opérations d'une monade
Une monade est un ensemble d'actions qui peuvent être composées avec l'attribut return
y >>=
opérateurs :
Ces deux opérateurs sont les opérateurs minimaux nécessaires pour définir une Monad
.
En Haskell, le >>
est également nécessaire mais il peut en fait être dérivé de l'opérateur >>=
:
(>>): Monad m => m a -> m b -> m b
a >> b = a >>= f
where f x = b
En Haskell, un supplément fail
est également nécessaire, mais il s'agit vraiment d'un hack (et de il pourrait être retiré de Monad
à l'avenir ).
C'est la définition Haskell d'un Monad
:
class Monad m where
return :: m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b -- can be derived from (>>=)
fail :: String -> m a -- mostly a hack
Les actions sont de premier ordre
L'un des avantages des monades est que les actions sont de première classe. Vous pouvez les prendre dans une variable, vous pouvez définir des fonctions qui prennent des actions en entrée et produisent d'autres actions en sortie. Par exemple, nous pouvons définir une while
opérateur :
-- while x y : does action y while action x output True
while :: (Monad m) => m Boolean -> m a -> m ()
while x y = x >>= f
where f True = y >> while x y
f False = return ()
Résumé
A Monad
est un ensemble de actions dans un certain domaine. La monade/le domaine définissent le type d'"effets" qui sont possibles. Le site >>
y >>=
Les opérateurs représentent le séquençage des actions et l'expression monadique peut être utilisée pour représenter tout type de "(sous-)programme impératif" dans votre programme Haskell (fonctionnel).
Les grandes choses sont que :
-
vous pouvez concevoir votre propre Monad
qui supporte les fonctionnalités et les effets que vous voulez
-
voir Prompt
pour un exemple de "sous-programme de dialogue uniquement",
-
voir Rand
pour un exemple de "sous-programme d'échantillonnage uniquement" ;
-
vous pouvez écrire vos propres structures de contrôle ( while
, throw
, catch
ou plus exotiques) comme des fonctions prenant des actions et les composant d'une certaine manière pour produire une macro-action plus importante.
MonadRandom
Un bon moyen de comprendre les monades est l'outil d'analyse de l'environnement. MonadRandom
paquet. Le site Rand
La monade est constituée d'actions dont la sortie peut être aléatoire (l'effet est le caractère aléatoire). Un site action dans cette monade est une sorte de variable aléatoire (ou plus exactement un processus d'échantillonnage) :
-- Sample an Int from some distribution
action :: Rand Int
Utilisation de Rand
pour faire de l'échantillonnage/des algorithmes aléatoires est assez intéressant car vous avez variables aléatoires comme des valeurs de premier ordre :
-- Estimate mean by sampling nsamples times the random variable x
sampleMean :: Real a => Int -> m a -> m a
sampleMean n x = ...
Dans ce contexte, le sequence
de la fonction Prelude
,
sequence :: Monad m => [m a] -> m [a]
devient
sequence :: [Rand a] -> Rand [a]
Il crée une variable aléatoire obtenue par échantillonnage indépendant à partir d'une liste de variables aléatoires.
0 votes
Cette série pourrait vous être utile, elle parle de CPS mais je pense que les aspects fonctionnels s'appliquent également ici. blogs.msdn.com/b/ericlippert/archive/tags/
3 votes
Vous supposez beaucoup de choses sur ce que les monades sont censées être. Mais elles sont bien plus générales que cela, et à peu près tout ce que vous citez n'est qu'une application spécifique du concept général. Toutes les monades ne sont pas non déterministes, toutes les monades ne tournent pas autour des effets secondaires, etc. Lire haskell.org/haskellwiki/What_a_Monad_is_not
1 votes
Cela ne sert à rien de savoir ce que ce n'est pas. En fait, je n'ai pas vu beaucoup d'exemples de Monads au-delà de IO et Maybe. C'est bien que ceux-ci aient été implémentés avec des fonctionnalités du langage, mais quelles sont ses autres utilisations au-delà de IO ? Si un livre consacre la moitié de ses pages aux Monads, il doit bien y avoir quelque chose d'utile à leur sujet.
2 votes
Cette question est valable en soi, mais différente de celle que vous avez posée. Il y a
Reader
,Writer
,State
,[]
(liste), les continuations, diverses monades pour l'analyse syntaxique, les monades pour la création de HTML/JS/CSS dans les applications web, et probablement beaucoup d'autres. De plus, je dirais qu'il est utile de comprendre ce que les monades ne sont pas - vous ne verrez pas toute la puissance des ordinateurs si vous êtes convaincu que leur seule utilité est de faire de l'arithmétique plus rapidement ;)