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

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

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

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

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

Ответы [ 45 ]

5 голосов
/ 16 мая 2009

Две вещи, которые помогли мне лучше всего узнать об этом, были:

Глава 8, «Функциональные парсеры», из книги Грэма Хаттона Программирование на Haskell . На самом деле это вообще не касается монад, но если вы сможете проработать главу и по-настоящему понять все в ней, особенно то, как оценивается последовательность операций связывания, вы поймете внутренности монад. Ожидайте, что это займет несколько попыток.

Учебник Все о монадах . Это дает несколько хороших примеров их использования, и я должен сказать, что аналогия в Appendex, которую я работал для меня.

5 голосов
/ 17 марта 2013

Monoid, по-видимому, гарантирует, что все операции, определенные для Monoid и поддерживаемого типа, всегда будут возвращать поддерживаемый тип внутри Monoid. Например, любое число + любое число = число, без ошибок.

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

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

С другой стороны, если у нас есть функция, которая запускает ракеты, мы можем объединить ее с другими функциями, которые также запускают ракеты, потому что наша цель ясна - мы хотим запустить ракеты - но она победила попробуйте напечатать "Hello World" по какой-то странной причине.

В Haskell main имеет тип IO () или IO [()], это странное распределение, и я не буду его обсуждать, но вот что я думаю:

Если у меня есть main, я хочу, чтобы он выполнял цепочку действий, поэтому я запускаю программу, чтобы произвести эффект - обычно через IO. Таким образом, я могу объединить операции ввода-вывода в main для выполнения ввода-вывода, ничего больше.

Если я попытаюсь сделать что-то, что не «возвращает IO», программа будет жаловаться, что цепочка не течет, или в основном «Как это связано с тем, что мы пытаемся сделать - действие IO», По-видимому, вынуждает программиста сохранять ход мыслей, не отступая и не думая о стрельбе по ракетам, одновременно создавая алгоритмы сортировки, которая не работает.

По сути, Monads, похоже, подсказка компилятору, что «эй, вы знаете эту функцию, которая возвращает число здесь, она на самом деле не всегда работает, иногда она может генерировать Number, а иногда вообще ничего, просто имейте это в виду ". Зная это, если вы пытаетесь утвердить монадическое действие, монадическое действие может действовать как исключение времени компиляции, говоря «эй, это на самом деле не число, это МОЖЕТ быть числом, но вы не можете предположить это, сделать что-то чтобы убедиться, что поток является приемлемым. " что в значительной степени предотвращает непредсказуемое поведение программы.

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

Самая большая причина, которую я мог придумать для Monads, - это посмотреть на процедурный / ООП-код, и вы заметите, что вы не знаете, где начинается или заканчивается программа, все, что вы видите, - это много прыжков и много математики, магии и ракет. Вы не сможете поддерживать его, и, если сможете, вы потратите довольно много времени на то, чтобы сосредоточиться на всей программе, прежде чем сможете понять какую-либо ее часть, потому что модульность в этом контексте основана на взаимозависимых «разделах». кода, где код оптимизирован, чтобы быть как можно более связанными для обеспечения эффективности / взаимосвязи. Монады очень конкретны и хорошо определены по определению и обеспечивают возможность анализа потока программы и выделения частей, которые трудно анализировать - так как они сами являются монадами. Монада представляется «понятной единицей, которая предсказуема при ее полном понимании». Если вы понимаете монаду «Возможно», то нет никакого способа, которым она будет делать что-либо, кроме как «Может быть», что кажется тривиальным, но в большинстве не монадических код, простая функция «helloworld» может запускать ракеты, ничего не делать, или разрушать вселенную, или даже искажать время - мы не имеем ни малейшего представления о том, ЧТО ЭТО ТАКОЕ. Монада ГАРАНТИРУЕТ, ЧТО ЭТО ТАКОЕ. который очень мощный.

Все вещи в "реальном мире" кажутся монадами в том смысле, что они связаны определенными наблюдаемыми законами, предотвращающими путаницу. Это не означает, что мы должны имитировать все операции этого объекта для создания классов, вместо этого мы можем просто сказать «квадрат есть квадрат», ничего, кроме квадрата, даже не прямоугольника и не круга, и «квадрат имеет площадь длины одного из его существующих измерений, умноженных на себя. Независимо от того, какой у вас квадрат, если это квадрат в 2D-пространстве, его площадь абсолютно не может быть ничем, кроме длины в квадрате, это почти тривиально доказать. нам не нужно делать утверждения, чтобы убедиться, что наш мир такой, какой он есть, мы просто используем значение реальности, чтобы не допустить срыва наших программ.

Я гарантированно ошибаюсь, но я думаю, что это может кому-то помочь, так что, надеюсь, это кому-нибудь поможет.

5 голосов
/ 20 декабря 2013

В контексте Scala вы найдете следующее определение. По сути, flatMap (или связывание) является «ассоциативным» и существует тождество.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

Е.Г.

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

ПРИМЕЧАНИЕ Строго говоря, определение Монады в функциональном программировании не совпадает с определением Монады в Теории категорий , которая определена обороты map и flatten. Хотя они являются своего рода эквивалентом при определенных отображениях. Это презентации очень хорошо: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

5 голосов
/ 08 марта 2015

Этот ответ начинается с мотивирующего примера, проходит через пример, выводит пример монады и формально определяет «монаду».

Рассмотрим эти три функции в псевдокоде:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

f принимает упорядоченную пару вида <x, messages> и возвращает упорядоченную пару. Он оставляет первый элемент нетронутым и добавляет "called f. " ко второму элементу. То же самое с g.

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

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Вам не нравится тот факт, что f и g несут ответственность за добавление своих собственных сообщений журнала к предыдущей информации журнала. (Просто представьте ради аргумента, что вместо добавления строк f и g должны выполнять сложную логику для второго элемента пары. Было бы больно повторять эту сложную логику в двух или более - - разные функции.)

Вы предпочитаете писать более простые функции:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Но посмотрите, что происходит, когда вы их составляете:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

Проблема в том, что передача пары в функцию не дает вам того, что вы хотите. Но что, если бы вы могли передать пару в функцию:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Считайте feed(f, m) как «подача m в f». передать пару <x, messages> в функцию f означает передать x в f, получить <y, message> из f и вернуть <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Обратите внимание, что происходит, когда вы выполняете три функции с помощью своих функций:

Сначала: если вы переносите значение, а затем кормите полученную пару в функцию:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

Это то же самое, что передача значения в функцию.

Второе: если вы кормите пару в wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Это не меняет пару.

Третье: если вы определяете функцию, которая принимает x и передает g(x) в f:

h(x) := feed(f, g(x))

и введите в нее пару:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

Это то же самое, что вводить пару в g и вводить полученную пару в f.

У вас большая часть монады. Теперь вам просто нужно знать о типах данных в вашей программе.

Какой тип значения <x, "called f. ">? Ну, это зависит от того, какой тип значения x. Если x имеет тип t, то ваша пара представляет собой значение типа "pair of t and string". Назовите этот тип M t.

M является конструктором типа: M сам по себе не относится к типу, но M _ относится к типу после заполнения пробела с типом. M int представляет собой пару типа int и строки. M string - это пара строки и строки. И т.д.

Поздравляем, вы создали монаду!

Формально ваша монада - это кортеж <M, feed, wrap>.

Монада - это кортеж <M, feed, wrap>, где:

  • M является конструктором типов.
  • feed принимает (функция, которая принимает t и возвращает M u) и M t и возвращает M u.
  • wrap принимает v и возвращает M v.

t, u и v - это любые три типа, которые могут быть или не быть одинаковыми. Монада удовлетворяет трем свойствам, которые вы доказали для своей конкретной монады:

  • Подача обернутого t в функцию аналогична передаче развернутого t в функцию.

    Формально: feed(f, wrap(x)) = f(x)

  • Подача M t в wrap ничего не делает для M t.

    Формально: feed(wrap, m) = m

  • Ввод M t (назовите его m) в функцию, которая

    • передает t в g
    • получает M u (назовите его n) от g
    • подаёт n в f

    совпадает с

    • подача m в g
    • получение n от g
    • подача n в f

    Формально: feed(h, m) = feed(f, feed(g, m)), где h(x) := feed(f, g(x))

Как правило, feed называется bind (AKA >>= в Haskell), а wrap называется return.

4 голосов
/ 03 августа 2017

Я попытаюсь объяснить Monad в контексте Haskell.

В функциональном программировании важен состав функций. Это позволяет нашей программе состоять из небольших, легко читаемых функций.

Допустим, у нас есть две функции: g :: Int -> String и f :: String -> Bool.

Мы можем сделать (f . g) x, что аналогично f (g x), где x - это значение Int.

При выполнении композиции / применении результата одной функции к другой важно, чтобы типы соответствовали друг другу. В приведенном выше случае тип результата, возвращаемого g, должен совпадать с типом, принятым f.

Но иногда значения находятся в контекстах, и это немного затрудняет выравнивание типов. (Наличие значений в контекстах очень полезно. Например, тип Maybe Int представляет значение Int, которого может не быть, тип IO String представляет значение String, которое существует в результате выполнения некоторой стороны эффекты.)

Допустим, у нас теперь есть g1 :: Int -> Maybe String и f1 :: String -> Maybe Bool. g1 и f1 очень похожи на g и f соответственно.

Мы не можем сделать (f1 . g1) x или f1 (g1 x), где x - это значение Int. Тип результата, возвращаемого g1, не соответствует ожидаемому f1.

Мы могли бы составить f и g с помощью оператора ., но теперь мы не можем составить f1 и g1 с .. Проблема в том, что мы не можем напрямую передать значение в контексте функции, которая ожидает значение, которое не находится в контексте.

Не было бы неплохо, если бы мы ввели оператор для составления g1 и f1, такой, что мы могли бы написать (f1 OPERATOR g1) x? g1 возвращает значение в контексте. Значение будет изъято из контекста и применено к f1. И да, у нас есть такой оператор. Это <=<.

У нас также есть оператор >>=, который делает для нас точно такую ​​же вещь, хотя и в несколько ином синтаксисе.

Пишем: g1 x >>= f1. g1 x является значением Maybe Int. Оператор >>= помогает вывести это значение Int из контекста "возможно, не там" и применить его к f1. Результат f1, то есть Maybe Bool, будет результатом всей операции >>=.

И, наконец, почему Monad полезен? Поскольку Monad - это класс типов, который определяет оператор >>=, очень похожий на класс типов Eq, который определяет операторы == и /=.

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

Если здесь есть что вспомнить, это то, что Monad s разрешают композицию функций, которая включает значения в контекстах .

4 голосов
/ 23 октября 2014

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

Sierpinski triangle

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

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

4 голосов
/ 08 сентября 2009

http://code.google.com/p/monad-tutorial/ находится в стадии разработки, чтобы ответить именно на этот вопрос.

3 голосов
/ 03 марта 2018

ТЛ; др

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Пролог

Оператор приложения $ функций

forall a b. a -> b

канонически определено

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

в терминах применения функции на Хаскелле f x (infixl 10).

Композиция . определяется в терминах $ как

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

и удовлетворяет эквивалентности forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

. является ассоциативным, а id - его правая и левая идентичность.

Тройной Клейсли

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

Функтор - это конструктор типа f вида * -> * с экземпляром класса типа функтора.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

В дополнение к следующему статически обязательному протоколу типов экземпляры класса функторов должны подчиняться алгебраическим законам функторов forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Функтор Вычисления имеют тип

forall f t. Functor f => f t

Вычисление c r состоит из результатов r в контексте c.

Унарные монадические функции или Стрелки Клейсли имеют тип

forall m a b. Functor m => a -> m b

Стрелки Kleisi - это функции, которые принимают один аргумент a и возвращают монадическое вычисление m b.

Монады канонически определены в терминах тройки Клейсли forall m. Functor m =>

(m, return, (=<<))

реализовано как класс типов

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

infixr 1 =<<

Идентификатор Клейсли return - это стрелка Клейсли, которая переводит значение t в монадический контекст m. Расширение или Приложение Kleisli =<< применяет стрелку Клейсли a -> m b к результатам вычисления m a.

Клейсли состав <=< определяется в терминах расширения как

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< составляет две стрелки Клейсли, применяя левую стрелку к результатам применения правой стрелки.

Экземпляры класса типа монады должны подчиняться законам монады , наиболее элегантно сформулированным с точки зрения композиции Клейсли: forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=< является ассоциативным, а return - его правая и левая идентичность.

Идентичность

Тип личности

type Id t = t

- это функция тождественности для типов

Id :: * -> *

интерпретируется как функтор,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

В каноническом Хаскеле тождественная монада определена

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Опция

Тип опции

data Maybe t = Nothing | Just t

кодирует вычисления Maybe t, которые не обязательно дают результат t, вычисления, которые могут «потерпеть неудачу». Опция монада определена

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe b применяется к результату, только если Maybe a дает результат.

newtype Nat = Nat Int

Натуральные числа могут быть закодированы как целые числа, большие или равные нулю.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Натуральные числа не вычитаются при вычитании.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

Монада опций охватывает базовую форму обработки исключений.

(-? 20) <=< toNat :: Int -> Maybe Nat

Список

Монада списка, над типом списка

data [] t = [] | t : [t]

infixr 5 :

и его аддитивная моноидная операция «добавление»

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

кодирует нелинейное вычисление [t], получая натуральное количество 0, 1, ... результатов t.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

Расширение =<< объединяет ++ все списки [b], полученные в результате применения f x стрелки Клейсли a -> [b] к элементам [a] в единый список результатов [b].

Пусть правильные делители натурального числа n будут

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

тогда

forall n.  let { f = f <=< divisors } in f n   =   []

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

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

Для простоты в этом объяснении используется иерархия классов типов

class              Functor f
class Functor m => Monad m

В Haskell текущая стандартная иерархия

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

Бекиспользовать не только каждую монаду - функтор, но каждая аппликативная функция - функтор, и каждая монада также аппликативная.

Использование монады списка, императивного псевдокода

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

примерно переводится в блок do ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

эквивалент понимание монады ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

и выражение

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

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

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

, где

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

Функция защиты определена

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

где тип блока или «пустой кортеж»

data () = ()

Аддитивные монады , поддерживающие выбор и сбой , могут быть абстрагированы с использованием класса типов

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

, где fail и <|> образуют моноид forall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

и fail - поглощающий / аннигилирующий нулевой элемент аддитивных монад

_ =<< fail  =  fail

Если в

guard (even p) >> return p

even p истинно, тогда охранник выдает [()], и, по определению >>, локальную постоянную функцию

\ _ -> return p

применяется к результату (). Если ложь, то охранник создает список монад fail ([]), который не дает результата для стрелки Клейсли, к которой нужно применить >>, так что этот p пропускается.

Государство

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

A процессор состояний является функцией

forall st t. st -> (t, st)

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

Тип государственных процессоров обычно называется

type State st t = st -> (t, st)

Монада процессора состояний - это * -> * функтор State st. Клейсли стрелки состояния процессора монады являются функциями

forall st a b. a -> (State st) b

В каноническом Haskell определяется ленивая версия монады процессора состояний

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Процессор состояния запускается путем предоставления начального состояния:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

Доступ к состоянию обеспечивается примитивами get и put, методами абстракции над с сохранением состояния монад:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st | m -> st where
   get :: m st
   put :: st -> m ()

m -> st объявляет функциональную зависимость типа состояния st от монады m; что State t, например, определит тип состояния как t однозначно.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

с типом устройства, используемым аналогично void в C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets часто используется со средствами доступа к полям записи.

Монад состояния, эквивалентный переменной threading

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

, где s0 :: Int, - одинаково прозрачный, но бесконечно более элегантный и практичный

.
(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1) - это вычисление типа State Int (), за исключением его эффекта , эквивалентного return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

Закон ассоциативности монад можно записать в терминах >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

или

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

Как и в программировании, ориентированном на выражения (например, Rust), последний оператор блока представляет его доходность. Оператор связывания иногда называют «программируемой точкой с запятой».

Примитивы структуры управления итерациями из структурированного императивного программирования эмулируются монадически

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Вход / выход

data World

Монада процессора состояний мира ввода / вывода - это согласование чистого Хаскелла и реального мира с функциональной денотативной и императивной операционной семантикой. Близкий аналог собственно строгой реализации:

type IO t = World -> (t, World)

Взаимодействию способствуют нечистые примитивы

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

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

unsafePerformIO :: IO t -> t

Или, по крайней мере, должен.

Подпись типа программы на Haskell

main :: IO ()
main = putStrLn "Hello, World!"

расширяется до

World -> ((), World)

фудеятельность, которая преобразует мир.

Эпилог

Категория, в которой объекты относятся к типам Haskell, а морфизмы являются функциями между типами Haskell, это «быстрая и свободная» категория Hask.

Функтор T - это отображение из категории C в категорию D; для каждого объекта в C объект в D

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

и для каждого морфизма в C морфизм в D

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

, где X, Y - объекты в C. HomC(X, Y) является классом гомоморфизмов всех морфизмов X -> Y в C. Функтор должен сохранять индивидуальность и композицию морфизма, «структуру» C, в D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

категория Клейсли категории C дается тройкой Клейсли

<T, eta, _*>

эндофунктора

T : C -> C

(f), морфизм тождества eta (return) и оператор расширения * (=<<).

Каждый морфизм Клейсли в Hask

      f :  X -> T(Y)
      f :: a -> m b

оператором расширения

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

получает морфизм в категории Hask Клейсли

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Композиция в категории Клейсли .T дана в терминах расширения

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

и удовлетворяет аксиомам категории

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

который, применяя преобразования эквивалентности

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

в терминах расширения даны канонически

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Монады также могут быть определены в терминах не расширения Клейса, а естественного преобразования mu в программировании, называемом join. Монада определяется как mu как тройка над категорией C эндофунктора

     T :  C -> C
     f :: * -> *

и две природные трансформации

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

удовлетворяющих эквивалентности

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

Затем определяется класс типа монады

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

Каноническая mu реализация опции монады:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

Функция concat

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

является join монадой списка.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Реализации join можно перевести из формы расширения с использованием эквивалентности

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

Обратный перевод от mu к форме расширения дается

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

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

Ответ прост: как компьютерные ученые, мы оцениваем абстракцию ! Когда мы проектируем интерфейс к программному компоненту, мы хотим , чтобы он раскрыл как можно меньше информации о реализации. Мы хотим иметь возможность заменить реализацию многими альтернативами, многими другими «экземплярами» той же «концепции». Когда мы разрабатываем общий интерфейс для многих программных библиотек, еще более важно, чтобы у выбранного нами интерфейса были различные реализации. Это общность понятия монады, которое мы так высоко ценим, это , потому что теория категорий настолько абстрактна, что ее понятия так полезны для программирования.

Поэтому неудивительно, что обобщение монад, которое мы представляем ниже, также имеет тесную связь с теорией категорий. Но мы подчеркиваем, что наша цель очень практична: не «реализовать теорию категорий», а найти более общий способ структурирования библиотек комбинаторов. Нам просто повезло, что математики уже проделали большую часть работы за нас!

из Обобщая монады в стрелки Джон Хьюз

2 голосов
/ 10 февраля 2017

Пусть ниже "{| a |m}" представляет некоторый фрагмент монадических данных. Тип данных, который объявляет a:

        (I got an a!)
          /        
    {| a |m}

Функция, f, знает, как создать монаду, если бы она имела a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Здесь мы видим функцию, f, пытающуюся оценить монаду, но получает упрек.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Функция f находит способ извлечь a, используя >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Мало f знает, монада и >>= в сговоре.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

Но о чем они вообще говорят? Ну, это зависит от монады. Разговор только в абстрактной форме имеет ограниченное применение; Вы должны иметь некоторый опыт работы с конкретными монадами, чтобы конкретизировать понимание.

Например, тип данных Maybe

 data Maybe a = Nothing | Just a

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

При этом, если дело Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Но для случая Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Таким образом, монада Maybe позволяет вычислению продолжаться, если оно фактически содержит a, которое оно объявляет, но прерывает вычисление, если этого не происходит. Результат, однако, все еще является частью монадических данных, но не выводит f. По этой причине монада Maybe используется для представления контекста сбоя.

Разные монады ведут себя по-разному. Списки - это другие типы данных с монадическими экземплярами. Они ведут себя так:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

В этом случае функция знала, как составить список из своего ввода, но не знала, что делать с дополнительным вводом и дополнительными списками. Привязка >>= помогла f объединить несколько выходов. Я включил этот пример, чтобы показать, что, хотя >>= отвечает за извлечение a, он также имеет доступ к возможному выходному значению f. Действительно, он никогда не извлечет a, если не знает, что конечный вывод имеет тот же тип контекста.

Существуют и другие монады, которые используются для представления различных контекстов. Вот некоторые характеристики еще нескольких. У монады IO на самом деле нет a, но она знает парня и получит это a для вас. Монада State st имеет секретный тайник st, который она перейдет к f под столом, хотя f только что пришел с просьбой о a. Монада Reader r похожа на State st, хотя позволяет f смотреть только на r.

Смысл всего этого в том, что любой тип данных, который объявляется монадой, объявляет некоторый контекст вокруг извлечения значения из монады. Большой выигрыш от всего этого? Ну, это достаточно просто, чтобы составить расчет с каким-то контекстом. Однако, это может привести к путанице при объединении нескольких вычислений, нагруженных контекстом. Операции монады позаботятся о разрешении взаимодействий контекста, чтобы программисту не пришлось это делать.

Обратите внимание, что использование >>= облегчает беспорядок, отнимая часть автономии у f. То есть, в приведенном выше случае, например, Nothing, f больше не может решать, что делать в случае Nothing; он закодирован в >>=. Это компромисс. Если f необходимо было решить, что делать в случае Nothing, то f должна была быть функцией от Maybe a до Maybe b. В этом случае Maybe быть монадой не имеет значения.

Обратите внимание, однако, что иногда тип данных не экспортирует свои конструкторы (глядя на ваш IO), и если мы хотим работать с объявленным значением, у нас нет другого выбора, кроме как работать с его монадическим интерфейсом.

2 голосов
/ 08 декабря 2015

По существу и Практически , монады допускают вложение обратного вызова
(с взаимно-рекурсивным потоком (простите дефисы))
(составным (или разложимым) способом)
(с типом безопасности (иногда (в зависимости от языка)))
)))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))

например. это НЕ монада:

//JavaScript is 'Practical'
var getAllThree = 
         bind(getFirst, function(first){  
  return bind(getSecond,function(second){  
  return bind(getThird, function(third){  
    var fancyResult = // And now make do fancy 
                      // with first, second,
                      // and third 
    return RETURN(fancyResult);
  });});});  

Но монады разрешают такой код.
Монада - это набор типов для:
{bind,RETURN,maybe others I don't know...}.
Что является по существу несущественным, а практически непрактичным.

Так что теперь я могу использовать его:

var fancyResultReferenceOutsideOfMonad =  
  getAllThree(someKindOfInputAcceptableToOurGetFunctionsButProbablyAString);  

//Ignore this please, throwing away types, yay JavaScript:
//  RETURN = K
//  bind = \getterFn,cb -> 
//    \in -> let(result,newState) = getterFn(in) in cb(result)(newState)

Или разбить его:

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(fancyResult2){  
    return bind(getThird,    function(third){  
      var fancyResult3 = // And now make do fancy 
                         // with fancyResult2,
                         // and third 
      return RETURN(fancyResult3);
    });});

Или игнорировать определенные результаты:

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(____dontCare____NotGonnaUse____){  
    return bind(getThird,    function(three){  
      var fancyResult3 = // And now make do fancy 
                         // with `three` only!
      return RETURN(fancyResult3);
    });});

Или упростите тривиальный случай из:

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(_){  
    return bind(getThird,    function(three){  
      return RETURN(three);
    });});

Кому (с использованием «Правильная идентификация» ):

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(_){  
    return getThird;
    });

Или замять их вместе:

var getAllThree = 
           bind(getFirst, function(first_dontCareNow){  
    return bind(getSecond,function(second_dontCareNow){  
    return getThird;
    });});

Практичность этих способностей не проявляется,
или становитесь понятными, пока не попытаетесь решить действительно беспорядочные проблемы
как разбор, или загрузка модуля / ajax / ресурса.

Можете ли вы представить тысячи строк логики indexOf / subString?
Что если частые шаги синтаксического анализа содержались в маленьких функциях?
Такие функции, как chars, spaces, upperChars или digits?
А что, если эти функции дали вам результат в обратном вызове,
без необходимости связываться с группами Regex и arguments.slice?
А что если их состав / разложение было хорошо понято?
Чтобы вы могли создавать большие парсеры снизу вверх?

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

НЕ ПОДНИМАЙТЕСЬ НА:
- КАТЕГОРИЯ-ТЕОРИЯ
- MAYBE-MONADS
- МОНАДНЫЕ ЗАКОНЫ
- HASKELL
- !!!!

...