Что такое монада? - PullRequest
       292

Что такое монада?

1330 голосов
/ 05 сентября 2008

Кратко рассмотрев недавно Хаскелла, каким было бы краткое, краткое, практичное объяснение того, что в сущности является монадой?

Я обнаружил, что большинство объяснений, с которыми я столкнулся, было довольно недоступным и лишено практических деталей.

Ответы [ 45 ]

0 голосов
/ 04 августа 2018

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

Конкретно, он позволяет вам выполнять join типа Monad m => m (m a) -> m a.

Также необходимо действие return, которое просто оборачивает значение. return :: Monad m => a -> m a
Вы также можете сказать join распаковки и return обертывания - но join это , а не типа Monad m => m a -> a (он не разворачивает все монады, он разворачивает монады с монадами внутри них.)

Таким образом, он берет коробку Монады (Monad m =>, m) с коробкой внутри нее ((m a)) и создает нормальную коробку (m a).

Однако, как правило, монада используется в терминах оператора (>>=) (разговорный «связывание»), который, по сути, просто fmap и join друг за другом. В частности,

x >>= f = join (fmap f x)
(>>=) :: Monad m => (a -> m b) -> m a -> m b

Обратите внимание, что функция приходит во втором аргументе, в отличие от fmap.

Также join = (>>= id).

Теперь, почему это полезно? По сути, он позволяет создавать программы, объединяющие действия вместе, работая в какой-то среде (Monad).

Самым заметным применением Монад в Хаскеле является IO Монада.
Теперь IO - это тип, который классифицирует Action в Haskell. Здесь система Монады была единственным способом сохранения (большие красивые слова):

  • Ссылочная прозрачность
  • ленивость

По сути, действие ввода-вывода, такое как getLine :: IO String, не может быть заменено строкой, так как оно всегда имеет другой тип. Думайте о IO как о волшебной шкатулке, которая телепортирует вещи к вам.
Тем не менее, просто сказав, что getLine :: IO String и все функции принимают IO a, возникает хаос, поскольку, возможно, функции не понадобятся Что бы const "üp§" getLine сделал? (const отбрасывает второй аргумент. const a b = a.) getLine не нужно оценивать, но он должен выполнять IO! Это делает поведение довольно непредсказуемым, а также делает систему типов менее «чистой», поскольку все функции будут принимать значения a и IO a.

Введите IO Монаду.

Чтобы связать действия вместе, вы просто выравниваете вложенные действия.
И чтобы применить функцию к выводу действия ввода-вывода, a в типе IO a, вы просто используете (>>=).

Например, для вывода введенной строки (для вывода строки используется функция, которая производит IO-действие, соответствующее правому аргументу >>=):

getLine >>= putStrLn :: IO ()
-- putStrLn :: String -> IO ()

Это может быть написано более интуитивно с окружением do:

do line <- getLine
   putStrLn line

По сути, блок do выглядит так:

do x <- a
   y <- b
   z <- f x y
   w <- g z
   h x
   k <- h z
   l k w

... превращается в это:

a     >>= \x ->
b     >>= \y ->
f x y >>= \z ->
g z   >>= \w ->
h x   >>= \_ ->
h z   >>= \k ->
l k w

Существует также оператор >> для m >>= \_ -> f (когда значение в блоке не требуется для создания нового блока в блоке) Также можно написать a >> b = a >>= const b (const a b = a)

Кроме того, оператор return моделируется после IO-intuituion - он возвращает значение с минимальным контекстом , в данном случае без IO . Поскольку a в IO a представляет возвращаемый тип, это похоже на что-то вроде return(a) в императивных языках программирования - но оно не останавливает цепочку действий! f >>= return >>= g равно то же , что и f >>= g. Это полезно только в том случае, если возвращаемый вами термин был создан ранее в цепочке - см. Выше.

Конечно, есть и другие монады, в противном случае это не будет называться монадой, это будет называться чем-то вроде "IO Control".

Например, монада списка (Monad []) сглаживается путем конкатенации - оператор (>>=) выполняет функцию для всех элементов списка. Это можно рассматривать как «индетерминизм», где List - это множество возможных значений, а Monad Framework создает все возможные комбинации.

Например (в GHCi):

Prelude> [1, 2, 3] >>= replicate 3  -- Simple binding
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> concat (map (replicate 3) [1, 2, 3])  -- Same operation, more explicit
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> [1, 2, 3] >> "uq"
"uququq"
Prelude> return 2 :: [Int]
[2]
Prelude> join [[1, 2], [3, 4]]
[1, 2, 3, 4]

, потому что:

join a = concat a
a >>= f = join (fmap f a)
return a = [a]  -- or "= (:[])"

Монада Maybe просто аннулирует все результаты до Nothing, если это когда-либо произойдет. То есть привязка автоматически проверяет, возвращает ли функция (a >>= f) или значение (a >>= f) Nothing - и затем также возвращает Nothing.

join       Nothing  = Nothing
join (Just Nothing) = Nothing
join (Just x)       = x
a >>= f             = join (fmap f a)

или, более точно:

Nothing  >>= _      = Nothing
(Just x) >>= f      = f x

Монада состояний предназначена для функций, которые также изменяют некоторое общее состояние - s -> (a, s), поэтому аргумент >>= равен :: a -> s -> (a, s).
Название является своего рода неправильным, поскольку State действительно для функций, изменяющих состояние, а не для состояния - само состояние действительно не имеет интересных свойств, оно просто изменяется.

Например:

pop ::       [a] -> (a , [a])
pop (h:t) = (h, t)
sPop = state pop   -- The module for State exports no State constructor,
                   -- only a state function

push :: a -> [a] -> ((), [a])
push x l  = ((), x : l)
sPush = state push

swap = do a <- sPop
          b <- sPop
          sPush a
          sPush b

get2 = do a <- sPop
          b <- sPop
          return (a, b)

getswapped = do swap
                get2

, то:

Main*> runState swap [1, 2, 3]
((), [2, 1, 3])
Main*> runState get2 [1, 2, 3]
((1, 2), [1, 2, 3]
Main*> runState (swap >> get2) [1, 2, 3]
((2, 1), [2, 1, 3])
Main*> runState getswapped [1, 2, 3]
((2, 1), [2, 1, 3])

также:

Prelude> runState (return 0) 1
(0, 1)
0 голосов
/ 28 мая 2018

после вашего краткого, краткого, практического указания:

Самый простой способ понять монаду - это как применять / создавать функции в контексте. Допустим, у вас есть два вычисления, которые можно рассматривать как две математические функции f и g.

  • f берет строку и создает другую строку (возьмите первые две буквы)
  • g принимает строку и создает другую строку (преобразование в верхнем регистре)

Таким образом, на любом языке преобразование «взять первые две буквы и преобразовать их в верхний регистр» будет записано как g (f («некоторая строка»)). Таким образом, в мире чистых совершенных функций композиция - это просто: делать одно, а потом делать другое.

Но, скажем, мы живем в мире функций, которые могут потерпеть неудачу. Например: входная строка может быть длиной в один символ, поэтому f потерпит неудачу. Так что в этом случае

  • f берет строку и создает строку или ничего.
  • g создает строку, только если f не потерпел неудачу. В противном случае ничего не выдает

Итак, теперь g (f («некоторая строка»)) требуется дополнительная проверка: «Вычислить f, если он потерпит неудачу, g ничего не должен возвращать, иначе вычислить g»

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

Пусть Context [Sometype] будет вычислением Sometype в пределах Context . С учетом функций

  • f:: AnyType -> Context[Sometype]
  • g:: Sometype -> Context[AnyOtherType]

композиция g (f ()) должна быть прочитана как «вычислите f. В этом контексте сделайте несколько дополнительных вычислений, а затем вычислите g, если это имеет смысл в контексте»

0 голосов
/ 17 марта 2018

Если вы просите краткого практического объяснения чего-то столь абстрактного, тогда вы можете только надеяться на абстрактный ответ:

a -> b

- это один из способов вычисления от a с до b с. Вы можете связать вычисления, или составить их вместе:

(b -> c) -> (a -> b) -> (a -> c)

Более сложные вычисления требуют более сложных типов, например ::

a -> f b

- это тип вычислений от a с до b с, которые относятся к f с. Вы также можете составить их:

(b -> f c) -> (a -> f b) -> (a -> f c)

Оказывается, этот паттерн появляется буквально повсюду и обладает теми же свойствами, что и первая композиция выше (ассоциативность, правая и левая идентичность).

Нужно было дать этому шаблону имя, но тогда помогло бы узнать, что первая композиция формально характеризуется как Полугруппоидный ?

«Монады так же интересны и важны, как и скобки» (Олег Киселев)

0 голосов
/ 31 марта 2013

Математическое мышление

Для краткости: алгебраическая структура для объединения вычислений.

return data: создать вычисление, которое просто генерирует данные в мире монад.

(return data) >>= (return func): Второй параметр принимает первый параметр в качестве генератора данных и создает новые вычисления, которые объединяют их.

Вы можете подумать, что (>> =) и return не будут выполнять какие-либо вычисления самостоятельно. Они просто объединяют и создают вычисления.

Любое вычисление монады будет вычислено тогда и только тогда, когда main сработает.

0 голосов
/ 14 мая 2016

Объяснение

Это довольно просто, если объяснить в терминах C # / Java:

  1. Монада - это функция, которая принимает аргументы и возвращает специальный тип.

  2. Специальный тип, который возвращает эта монада, это , также , называемый монадой. (Монада - это комбинация № 1 и № 2)

  3. Существует некоторый синтаксический сахар, облегчающий вызов этой функции и преобразование типов.

Пример * * тысяча двадцать-одна Монада полезна для облегчения жизни функционального программиста. Типичный пример: монада Maybe принимает два параметра, значение и функцию. Возвращает null, если передано значение null. В противном случае он оценивает функцию. Если бы нам нужен был специальный тип возвращаемого значения, мы бы также назвали этот тип возвращаемого значения Maybe. Очень грубая реализация выглядела бы так: object Maybe(object value, Func<object,object> function) { if(value==null) return null; return function(value); } Это невероятно бесполезно в C #, потому что в этом языке нет необходимого синтаксического сахара, чтобы сделать монады полезными. Но монады позволяют писать более лаконичный код на функциональных языках программирования. Часто программисты называют монады в цепочках, например: var x = Maybe(x, x2 => Maybe(y, y2 => Add(x2, y2))); В этом примере метод Add будет вызываться только в том случае, если x и y не являются null, в противном случае будет возвращено null. Ответ

Чтобы ответить на первоначальный вопрос: монада - это функция И тип. Как реализация специального interface.

...