Поиск…


Вступление

Монада - это тип данных составных действий. Monad - это класс конструкторов типов, значения которых представляют такие действия. Возможно , IO является самым узнаваемым один: значение IO a является «рецептом для получения a значении от реального мира».

Мы говорим, что конструктор типа m (такой как [] или Maybe ) образует монаду, если есть instance Monad m удовлетворяющий некоторым законам о составе действий. Тогда мы можем рассуждать о ma как «действие, результат которого имеет тип a ».

Возможно, монада

Maybe , используется для представления возможных пустых значений - аналогично null на других языках. Обычно он используется как выходной тип функций, которые могут каким-то образом сбой.

Рассмотрим следующую функцию:

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

Подумайте о halve как действия, в зависимости от Int , который пытается вдвое уменьшить целое число, если нечетно.

Как мы halve число целых чисел?

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 - это последовательность из трех halve шагов, соединенных вместе.
  • Если шаг на halve не удался, мы хотим, чтобы вся композиция takeOneEighth потерпела неудачу.
  • Если шаг с halve успеха завершен, мы хотим передать его результат вперед.

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

и теперь мы можем написать:

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

Состав Клейсли <=< определяется как (g <=< f) x = g =<< fx или эквивалентно как (f >=> g) x = fx >>= g . При этом указанное выше определение становится

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

Существует три закона монады, которые должны соблюдаться каждой монадой, то есть каждый тип, который является экземпляром класса Monad :

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

где m - монада, f имеет тип a -> mb и g имеет тип b -> mc .

Или, что то же самое, используя оператор состава >=> Клейсли, определенный выше:

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 } }

Соблюдение этих законов значительно облегчает рассуждение о монаде, потому что оно гарантирует, что использование монадических функций и составление их ведет себя разумно, подобно другим монадам.

Давайте проверим, Maybe монада Maybe подчиняться трем законам монады.

  1. Закон левого тождества - return x >>= f = fx
return z >>= f 
= (Just z) >>= f 
= f z
  1. Правильный закон тождества - m >>= return = m
  • Just конструктор данных
Just z >>= return
= return z
= Just z  
  • Nothing конструктора данных
Nothing >>= return
= Nothing 
  1. Закон ассоциативности - (m >>= f) >>= g = m >>= (\x -> fx >>= g)
  • Just конструктор данных
-- 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
  • Nothing конструктора данных
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing

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

IO monad

Невозможно получить значение типа a из выражения типа IO a и его не должно быть. На самом деле это большая часть того, почему монады используются для моделирования IO .

Выражение типа IO a можно представить как представляющее действие, которое может взаимодействовать с реальным миром и, если оно выполняется, приведет к чему-то типа a . Например, функция getLine :: IO String из прелюдии не означает, что под getLine есть определенная строка, которую я могу извлечь - это означает, что getLine представляет действие получения строки со стандартного ввода.

Неудивительно, что main :: IO () поскольку программа Haskell представляет собой вычисление / действие, которое взаимодействует с реальным миром.

То, что вы можете сделать с выражениями типа IO a потому что IO является монадой:

  • Последовательность двух действий с использованием (>>) для создания нового действия, выполняющего первое действие, отбрасывает любое значение, которое оно произвело, а затем выполняет второе действие.

      -- print the lines "Hello" then "World" to stdout
      putStrLn "Hello" >> putStrLn "World"
    
  • Иногда вы не хотите отбрасывать значение, которое было создано в первом действии, - вам действительно хотелось бы, чтобы его подавали во второе действие. Для этого имеем >>= . Для IO он имеет тип (>>=) :: IO a -> (a -> IO b) -> IO b .

     -- get a line from stdin and print it back out
     getLine >>= putStrLn
    
  • Возьмите нормальное значение и преобразуйте его в действие, которое сразу же возвращает значение, которое вы ему дали. Эта функция менее очевидно , полезно , пока вы не начнете использовать do запись.

     -- make an action that just returns 5
     return 5
    

Больше из Haskell Wiki на монаде IO здесь .

Список Монад

Списки образуют монаду. У них есть экземпляр монады, эквивалентный этому:

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

Мы можем использовать их для подражания недетерминированности в наших вычислениях. Когда мы используем xs >>= f , функция f :: a -> [b] отображается в списке xs , получая список списков результатов каждого применения f по каждому элементу xs и всех списков результаты затем объединяются в один список всех результатов. В качестве примера мы вычисляем сумму двух недетерминированных чисел, используя do-notation , причем сумма представлена ​​списком сумм всех пар целых чисел из двух списков, каждый из которых представляет все возможные значения недетерминированного числа:

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

Или, что то же самое, используя liftM2 в Control.Monad :

sumnd = liftM2 (+)

мы получаем:

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

Монад как подкласс прикладной

Начиная с GHC 7.10, Applicative является суперклассом Monad (т. Monad Каждый тип, который является Monad также должен быть Applicative ). Все методы Applicative ( pure , <*> ) могут быть реализованы с точки зрения методов Monad ( return , >>= ).

Очевидно, что pure и return служат эквивалентным целям, поэтому pure = return . Определение для <*> слишком относительно ясно:

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]                   

Эта функция определяется как ap в стандартных библиотеках.

Таким образом, если вы уже определили экземпляр Monad для типа, вы можете получить экземпляр Applicative для него «бесплатно», указав

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

Как и в случае с монадскими законами, эти эквивалентности не применяются, но разработчики должны следить за тем, чтобы они всегда поддерживались.

Нет общего способа извлечь значение из монадического вычисления

Вы можете переносить значения в действия и обрабатывать результат одного вычисления в другом:

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

Однако определение Монады не гарантирует существования функции типа Monad m => ma -> a .

Это означает, что вообще нет способа извлечь значение из вычисления (т. Е. «Развернуть» его). Это касается многих случаев:

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

В частности, нет функции IO a -> a , которая часто путает новичков; см. этот пример .

делать-нотация

do -notation - синтаксический сахар для монад. Вот правила:

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

Например, эти определения эквивалентны:

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)

Определение Монады

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

Наиболее важной функцией для работы с монадами является оператор связывания >>= :

(>>=) :: m a -> (a -> m b) -> m b
  • Подумайте о ma , как «действия с a результате».
  • Подумайте a -> mb «действия ( в зависимости от как a параметре) с b . Результат».

>>= последовательно выполняет два действия, связывая результат от первого действия ко второму.

Другая функция, определенная Monad :

return :: a -> m a

Его имя несчастливо: это return имеет ничего общего с ключевым словом return найденным на императивных языках программирования.

return x - это тривиальное действие, приводящее к результату x . (Это тривиально в следующем смысле :)

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow