Recherche…


Introduction

Une monade est un type de données d'actions composables. Monad est la classe des constructeurs de types dont les valeurs représentent de telles actions. Peut-être IO est le plus reconnaissable d' un: une valeur de IO a une « recette pour récupérer une a valeur du monde réel ».

Nous disons qu'un constructeur de type m (tel que [] ou Maybe ) forme une monade s'il existe une instance Monad m satisfaisant certaines lois sur la composition des actions. On peut alors raisonner sur ma comme une "action dont le résultat a tapé a ".

La monade peut-être

Maybe - Maybe est-il utilisé pour représenter des valeurs éventuellement vides - similaires à null dans les autres langues. Habituellement, il est utilisé comme type de sortie des fonctions qui peuvent échouer d'une certaine manière.

Considérons la fonction suivante:

halve :: Int -> Maybe Int
halve x
  | even x = Just (x `div` 2)
  | odd x  = Nothing

Pensez à halve de halve en tant qu'action, en fonction d'un Int , qui tente de réduire de moitié l'entier, en cas d'échec s'il est impair.

Comment halve nous par un entier trois fois?

takeOneEighth :: Int -> Maybe Int            -- (after you read the 'do' sub-section:)
takeOneEighth x =                
  case halve x of                               --  do {
    Nothing -> Nothing
    Just oneHalf ->                             --     oneHalf    <- halve x
      case halve oneHalf of
        Nothing -> Nothing
        Just oneQuarter ->                      --     oneQuarter <- halve oneHalf
          case halve oneQuarter of
            Nothing -> Nothing                  --     oneEighth  <- halve oneQuarter
            Just oneEighth ->                         
              Just oneEighth                    --     return oneEighth }
  • takeOneEighth est une séquence de trois étapes de halve takeOneEighth halve .
  • Si une halve étape échoue, nous voulons que toute la composition takeOneEighth échoue.
  • Si une halve étape réussit, nous voulons que son résultat soit reporté.

instance Monad Maybe where
  -- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
  Nothing >>= f  = Nothing                            -- infixl 1 >>=
  Just x  >>= f  = Just (f x)                         -- also, f =<< m = m >>= f
  
  -- return :: a -> Maybe a
  return x       = Just x

et maintenant nous pouvons écrire:

takeOneEighth :: Int -> Maybe Int
takeOneEighth x = halve x >>= halve >>= halve             -- or,
    -- return x >>= halve >>= halve >>= halve             -- which is parsed as
    -- (((return x) >>= halve) >>= halve) >>= halve       -- which can also be written as
    -- (halve =<<) . (halve =<<) . (halve =<<) $ return x    -- or, equivalently, as
    --  halve <=<     halve <=<     halve      $        x

La composition de Kleisli <=< est définie comme (g <=< f) x = g =<< fx ou, de manière équivalente, comme (f >=> g) x = fx >>= g . Avec elle la définition ci-dessus devient juste

takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve               -- infixr 1 <=<
        -- or, equivalently,                    
        --      halve >=> halve >=> halve               -- infixr 1 >=>    

Chaque monade doit obéir à trois lois de la monade, c'est-à-dire à tout type qui est une instance de la classe Monad :

1.  return x >>= f  =  f x
2.    m >>= return  =  m
3. (m >>= g) >>= h  =  m >>= (\y -> g y >>= h)

m est une monade, f a le type a -> mb et g a le type b -> mc .

Ou de manière équivalente, en utilisant l'opérateur de composition >=> Kleisli défini ci-dessus:

1.    return >=> g  =  g                    -- do { y <- return x ; g y } == g x
2.    f >=> return  =  f                    -- do { y <- f x ; return y } == f x
3. (f >=> g) >=> h  =  f >=> (g >=> h)      -- do { z <- do { y <- f x; g y } ; h z }
                                            --  == do { y <- f x ; do { z <- g y; h z } }

L'obéissance à ces lois rend la raison de la monade beaucoup plus facile à comprendre, car elle garantit que l'utilisation de fonctions monadiques et leur composition se comportent de manière raisonnable, comme d'autres monades.

Vérifions si la monade Maybe obéit aux trois lois de la monade.

  1. La loi d'identité de gauche - return x >>= f = fx
return z >>= f 
= (Just z) >>= f 
= f z
  1. La bonne loi d'identité - m >>= return = m
  • Just constructeur de données
Just z >>= return
= return z
= Just z  
  • Constructeur de données Nothing
Nothing >>= return
= Nothing 
  1. La loi d'associativité - (m >>= f) >>= g = m >>= (\x -> fx >>= g)
  • Just constructeur de données
-- Left-hand side
((Just z) >>= f) >>= g
= f z >>= g

-- Right-hand side
(Just z) >>= (\x -> f x >>= g)
(\x -> f x >>= g) z
= f z >>= g
  • Constructeur de données Nothing
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing

-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing

IO monade

Il n'y a aucun moyen d'obtenir une valeur de type a sur une expression de type IO a et il ne devrait pas y en avoir. C'est en fait une grande partie de la raison pour laquelle les monades sont utilisées pour modéliser les IO .

Une expression de type IO a peut être considérée comme représentant une action pouvant interagir avec le monde réel et, si elle est exécutée, entraînerait quelque chose de type a . Par exemple, la fonction getLine :: IO String du prélude ne signifie pas que sous getLine il existe une chaîne spécifique que je peux extraire - cela signifie que getLine représente l'action getLine obtenir une ligne à partir d'une entrée standard.

Sans surprise, main :: IO () depuis un programme Haskell représente un calcul / une action qui interagit avec le monde réel.

Les choses que vous pouvez faire pour les expressions de type IO a car IO est une monade:

  • Séquence de deux actions en utilisant (>>) pour produire une nouvelle action qui exécute la première action, rejette la valeur produite, puis exécute la seconde action.

      -- print the lines "Hello" then "World" to stdout
      putStrLn "Hello" >> putStrLn "World"
    
  • Parfois, vous ne voulez pas ignorer la valeur qui a été produite lors de la première action. En fait, vous souhaitez qu’elle soit ajoutée à une seconde action. Pour cela, nous avons >>= . Pour IO , il a le type (>>=) :: IO a -> (a -> IO b) -> IO b .

     -- get a line from stdin and print it back out
     getLine >>= putStrLn
    
  • Prenez une valeur normale et convertissez-la en une action qui retourne immédiatement la valeur que vous lui avez donnée. Cette fonction est moins utile jusqu'à ce que vous commenciez à utiliser la notation do .

     -- make an action that just returns 5
     return 5
    

Plus d'informations sur le wiki Haskell sur la monade IO ici .

Liste Monad

Les listes forment une monade. Ils ont une instanciation de monade équivalente à celle-ci:

instance Monad [] where 
  return x = [x]
  xs >>= f = concat (map f xs)               

Nous pouvons les utiliser pour émuler le non-déterminisme dans nos calculs. Lorsque nous utilisons xs >>= f , la fonction f :: a -> [b] est mappée sur la liste xs , en obtenant une liste de résultats de chaque application de f sur chaque élément de xs , et toutes les listes de les résultats sont ensuite concaténés dans une liste de tous les résultats. A titre d'exemple, nous calculons une somme de deux nombres non déterministes en utilisant la notation do , la somme étant représentée par la liste des sommes de toutes les paires d'entiers de deux listes, chaque liste représentant toutes les valeurs possibles d'un nombre non déterministe:

sumnd xs ys = do
  x <- xs
  y <- ys
  return (x + y)

Ou, de manière équivalente, en utilisant liftM2 dans Control.Monad :

sumnd = liftM2 (+)

on obtient:

> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]

Monad comme sous-classe d'application

A partir de GHC 7.10, Applicative est une super-classe de Monad (c’est-à-dire que chaque type qui est une Monad doit également être un Applicative ). Toutes les méthodes de Applicative ( pure , <*> ) peuvent être implémentées en termes de méthodes de Monad ( return , >>= ).

Il est évident que le pure et le return servent des objectifs équivalents, donc pure = return . La définition de <*> est trop claire:

mf <*> mx = do { f <- mf; x <- mx; return (f x) }                 
       -- = mf >>= (\f -> mx >>= (\x -> return (f x)))
       -- = [r   | f <- mf, x <- mx, r <- return (f x)]   -- with MonadComprehensions
       -- = [f x | f <- mf, x <- mx]                   

Cette fonction est définie comme ap dans les bibliothèques standard.

Ainsi, si vous avez déjà défini une instance de Monad pour un type, vous pouvez effectivement obtenir une instance de Applicative "gratuitement" en définissant

instance Applicative < type > where
    pure  = return
    (<*>) = ap

Comme pour les lois monades, ces équivalences ne sont pas appliquées, mais les développeurs doivent s’assurer qu’elles sont toujours respectées.

Pas de moyen général d'extraire de la valeur d'un calcul monadique

Vous pouvez envelopper des valeurs dans des actions et transférer le résultat d'un calcul dans un autre:

return :: Monad m => a -> m a
(>>=)  :: Monad m => m a -> (a -> m b) -> m b

Cependant, la définition d'une Monade ne garantit pas l'existence d'une fonction de type Monad m => ma -> a .

Cela signifie qu’il n’existe en général aucun moyen d’extraire une valeur d’un calcul (par exemple, le «déplier»). C'est le cas pour de nombreuses instances:

extract :: Maybe a -> a
extract (Just x) = x          -- Sure, this works, but...
extract Nothing  = undefined  -- We can’t extract a value from failure.

Plus précisément, il n'y a pas de fonction IO a -> a , qui confond souvent les débutants; voir cet exemple .

do-notation

do -notation est le sucre syntaxique pour les monades. Voici les règles:

do x <- mx                               do x <- mx
   y <- my       is equivalent to           do y <- my
   ...                                         ...
do let a = b                             let a = b in
   ...           is equivalent to          do ...
do m                                     m >> (
   e             is equivalent to          e)
do x <- m                                m >>= (\x ->
   e             is equivalent to          e)
do m             is equivalent to        m

Par exemple, ces définitions sont équivalentes:

example :: IO Integer
example =
  putStrLn "What's your name?" >> (
    getLine >>= (\name ->
      putStrLn ("Hello, " ++ name ++ ".") >> (
        putStrLn "What should we return?" >> (
          getLine >>= (\line ->
            let n = (read line :: Integer) in
              return (n + n))))))

example :: IO Integer
example = do
  putStrLn "What's your name?"
  name <- getLine
  putStrLn ("Hello, " ++ name ++ ".")
  putStrLn "What should we return?"
  line <- getLine
  let n = (read line :: Integer)
  return (n + n)

Définition de Monade

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

La fonction la plus importante pour traiter les monades est l' opérateur de liaison >>= :

(>>=) :: m a -> (a -> m b) -> m b
  • Pensez à ma comme "une action avec a résultat" .
  • Considérez a -> mb comme «une action (dépendant d’ a paramètre) avec un résultat b .

>>= séquence deux actions en canalisant le résultat de la première action à la seconde.

L'autre fonction définie par Monad est:

return :: a -> m a

Son nom est regrettable: ce return n'a rien à voir avec le mot-clé de return trouvé dans les langages de programmation impératifs.

return x est l'action triviale donnant x comme résultat. (C'est trivial dans le sens suivant :)

return x >>= f       ≡  f x     --  “left identity” monad law
       x >>= return  ≡  x       -- “right identity” monad law


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow