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

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

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

+1359
источник поделиться
48 ответов
  • 1
  • 2

Во-первых: термин монада немного бессмыслен, если вы не математик. Альтернативный термин - построитель вычислений, который немного больше описывает то, для чего они действительно полезны.

Вы просите практических примеров:

Пример 1: Понимание списка:

[x*2 | x<-[1..10], odd x]

Это выражение возвращает двойные числа всех нечетных чисел в диапазоне от 1 до 10. Очень полезно!

Оказывается, это действительно просто синтаксический сахар для некоторых операций в монаде List. Такое же понимание списка можно записать так:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

Или даже:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Пример 2: ввод/вывод:

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

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

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

Другим примером являются исключения: при использовании монады Error операции объединяются в цепочку так, что они выполняются последовательно, за исключением случаев, когда выдается ошибка, и в этом случае оставшаяся часть цепочки отменяется.

Синтаксис осмысления списка и нотация do являются синтаксическим сахаром для операций цепочки с использованием оператора >>=. Монада - это просто тип, который поддерживает оператор >>=.

Пример 3: парсер

Это очень простой парсер, который анализирует либо строку в кавычках, либо число:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

Операции char, digit и т.д. Довольно просты. Они либо совпадают, либо не совпадают. Волшебство - это монада, которая управляет потоком управления: операции выполняются последовательно до тех пор, пока совпадение не завершится, и в этом случае монада возвращается к последнему значению <|> и пробует следующую опцию. Опять же, способ объединения операций с некоторой дополнительной полезной семантикой.

Пример 4: Асинхронное программирование

Приведенные выше примеры есть в Haskell, но, оказывается, F # также поддерживает монады. Этот пример украден у Дона Сайма:

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

Этот метод выбирает веб-страницу. GetResponseAsync является использование GetResponseAsync - он фактически ожидает ответа в отдельном потоке, в то время как основной поток возвращается из функции. Последние три строки выполняются в порожденном потоке, когда ответ получен.

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

Как они работают

Так как же монада может делать все эти причудливые вещи управления потоком? Что действительно происходит в do-блоке (или вычислительном выражении, как они называются в F #), так это то, что каждая операция (в основном каждая строка) заключена в отдельную анонимную функцию. Эти функции затем объединяются с помощью оператора bind (пишется >>= в Haskell). Поскольку операция bind объединяет функции, она может выполнять их так, как считает нужным: последовательно, многократно, в обратном порядке, отбрасывать некоторые, выполнять некоторые в отдельном потоке, когда чувствует себя так, и так далее.

В качестве примера, это расширенная версия IO-кода из примера 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

Это уродливее, но также более очевидно, что на самом деле происходит. Оператор >>= является магическим компонентом: он принимает значение (слева) и объединяет его с функцией (справа), чтобы получить новое значение. Это новое значение затем берется оператором next >>= и снова объединяется с функцией для получения нового значения. >>= можно рассматривать как мини-оценщик.

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

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

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

Подводя итоги

В терминах Haskell монада - это параметризованный тип, который является экземпляром класса типов Monad, который определяет >>= вместе с несколькими другими операторами. С точки зрения непрофессионала, монада - это просто тип, для которого определена операция >>=.

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

Почему монады трудны?

Для многих изучающих Хаскель монады являются препятствием, которое они наносят, как кирпичная стена. Дело не в том, что сами монады являются сложными, а в том, что реализация опирается на многие другие расширенные функции Haskell, такие как параметризованные типы, классы типов и так далее. Проблема в том, что ввод-вывод Haskell основан на монадах, и ввод-вывод, вероятно, является одной из первых вещей, которую вы хотите понять при изучении нового языка - в конце концов, создавать программы, которые не производят никаких программ, не очень интересно. выход. У меня нет немедленного решения этой проблемы "курица-яйцо", за исключением обработки ввода-вывода как "волшебство происходит здесь", пока у вас не будет достаточного опыта работы с другими частями языка. Сожалею.

Отличный блог о монадах: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

+1028
источник

Объяснение "что такое монада" - это немного похоже на высказывание "что такое число?". Мы постоянно используем номера. Но представьте, что вы встретили человека, который ничего не знал о цифрах. Как вы могли бы объяснить, какие цифры? И как бы вы даже начали описывать, почему это может быть полезно?

Что такое монада? Короткий ответ: Это особый способ объединения операций.

По сути, вы пишете шаги выполнения и связываете их вместе со "функцией связывания". (В Haskell это называется >>=.) Вы можете сами написать вызовы оператору связывания, или вы можете использовать синтаксический сахар, который заставляет компилятор вставлять эти вызовы функций для вас. Но в любом случае каждый шаг разделяется вызовом этой функции связывания.

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

Это звучит не слишком сложно, не так ли? Но существует более одного вида монады. Зачем? Как?

Ну, функция bind может просто взять результат с одного шага и передать его на следующий шаг. Но если это "все" монада делает... это на самом деле не очень полезно. И это важно понимать: каждая полезная монада делает что-то еще в дополнение к тому, чтобы просто быть монадой. Каждая полезная монада имеет "особую силу", которая делает ее уникальной.

(Монада, которая ничего не делает ничего особенного, называется "тождественной монадой". Скорее, как функция идентичности, это звучит как совершенно бессмысленная вещь, но не получается... Но эта другая история и торговля;)

В принципе, каждая монада имеет свою собственную реализацию функции bind. И вы можете написать функцию связывания, чтобы она делала ошибки между шагами выполнения. Например:

  • Если каждый шаг возвращает индикатор успеха/сбоя, вы можете связать выполнение следующего шага только в том случае, если предыдущий преуспел. Таким образом, неудачный шаг прерывает всю последовательность "автоматически", без каких-либо условных проверок от вас. ( Failure Monad.)

  • Расширяя эту идею, вы можете реализовать "исключения". ( Ошибка Monad или Исключение Monad.) Поскольку вы определяете их самостоятельно, а не являетесь языковой функцией, вы можете определить, как они работают. (Например, возможно, вы хотите игнорировать первые два исключения и только прервать, когда выбрано третье исключение.)

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

  • Как и передача "результата" с одного шага на другой, вы также можете передать функцию привязки и дополнительные данные. Эти данные теперь не отображаются в исходном коде, но вы все равно можете получить доступ к нему из любого места, без необходимости вручную передавать его каждой функции. ( Reader Monad.)

  • Вы можете сделать так, чтобы "дополнительные данные" можно было заменить. Это позволяет имитировать деструктивные обновления, не производя при этом деструктивных обновлений. ( State Monad и его двоюродный брат Writer Monad.)

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

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

  • Вы можете реализовать "продолжения" в качестве монады. Это позволяет вам нарушать умы людей!

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

+699
источник

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

Например, вы можете создать тип для переноса другого, в Haskell:

data Wrapped a = Wrap a

Чтобы обернуть материал, мы определяем

return :: a -> Wrapped a
return x = Wrap x

Чтобы выполнить операции без разворачивания, скажем, у вас есть функция f :: a -> b, тогда вы можете сделать это, чтобы поднять эту функцию, чтобы действовать на завернутые значения:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

Это обо всем, что нужно понять. Однако оказывается, что для этого подъема существует более общая функция, которая равна bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bind может сделать немного больше, чем fmap, но не наоборот. На самом деле, fmap можно определить только в терминах bind и return. Итак, при определении монады.. вы указываете ее тип (здесь это был Wrapped a), а затем говорите, как работают его операции return и bind.

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

Для хорошей статьи о том, как монады могут использоваться для введения функциональных зависимостей и, таким образом, контролировать порядок оценки, например, он используется в монаде Haskell IO, проверьте IO Inside.

Что касается понимания монадов, не беспокойтесь об этом слишком много. Читайте о них, что вы считаете интересными, и не волнуйтесь, если вы не сразу поймете. Тогда просто погружение на языке, таком как Haskell, - это путь. Монады - одна из тех вещей, где понимание проникает в ваш мозг практикой, однажды вы просто осознаете, что понимаете их.

+182
источник

Но, Вы могли бы придумать Monads!

sigfpe говорит:

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

     

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

     

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

+168
источник

Монада - это тип данных, который имеет две операции: >>= (aka bind) и return (aka unit). return принимает произвольное значение и создает экземпляр монады с ним. >>= берет экземпляр монады и отображает над ней функцию. (Вы можете видеть, что монада - это странный тип данных, поскольку на большинстве языков программирования вы не могли написать функцию, которая принимает произвольное значение и создает из нее тип. Монады используют вид параметрический полиморфизм.)

В обозначении Haskell интерфейс монады записывается

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

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

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

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

где [] и : - конструкторы списка, ++ - оператор конкатенации, а Just и Nothing - конструкторы Maybe. Обе эти монады инкапсулируют общие и полезные шаблоны вычислений по их соответствующим типам данных (обратите внимание, что ни одно из них не имеет ничего общего с побочными эффектами или ввода-выводами).

Вам действительно нужно поиграть с каким-то нетривиальным кодом Haskell, чтобы оценить, что такое монады и почему они полезны.

+86
источник

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

Функция более высокого порядка - это просто функция, которая принимает функцию в качестве аргумента.

Функтором является любая конструкция типа T для которой существует функция более высокого порядка, назовем ее map, которая преобразует функцию типа a → b (заданную любыми двумя типами a и b) в функцию T a → T b. Эта функция map также должна подчиняться законам идентичности и композиции таким образом, что следующие выражения возвращают true для всех p и q (обозначение Haskell):

map id = id
map (p . q) = map p . map q

Например, конструктор типа List является функтором, если он оснащен функцией типа (a → b) → List a → List b которая подчиняется законам выше. Единственная практическая реализация очевидна. Полученная в результате функция List a → List b выполняет итерацию по данному списку, вызывая (a → b) для каждого элемента и возвращает список результатов.

Монада является по существу просто функтором T с двумя дополнительными методами, join тип T (T a) → T a и unit (иногда называемую return, fork или pure) типа a → T a. Для списков в Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Почему это полезно? Так как вы можете, например, map список с функцией, которая возвращает список. Join принимает список списков и объединяет их. List - монада, потому что это возможно.

Вы можете написать функцию, которая map, а затем join. Эта функция называется bind, или flatMap, или (>>=), или (=<<). Обычно это как экземпляр монады дается в Haskell.

Монада должна удовлетворять определенным законам, а именно, что join должно быть ассоциативным. Это означает, что если у вас есть значение x типа [[[a]]] тогда join (join x) должен равняться join (map join x). И pure должно быть тождеством для join таких, что join (pure x) == x.

+77
источник

[Отказ: я все еще пытаюсь полностью собрать монады. Следующее - это то, что я понял до сих пор. Если его неправильно, мы надеемся, что кто-то знающий будет называть меня на ковер.]

Арнар писал (а):

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

Вот именно это. Идея такова:

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

    Например, дополнительная информация может быть Maybe или IO.

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

    Например, a Maybe Int может быть Just Int или Nothing. Теперь, если вы добавите Maybe Int в Maybe Int, оператор проверит, будут ли они оба Just Int внутри, и если да, то разворачивает Int s, передает им оператор добавления, заверните полученный Int в новый Just Int (который является допустимым Maybe Int) и, таким образом, верните a Maybe Int. Но если один из них был Nothing внутри, этот оператор сразу же вернет Nothing, что снова является допустимым Maybe Int. Таким образом, вы можете притворяться, что ваши Maybe Int являются просто нормальными числами и выполняют на них регулярную математику. Если вам нужно получить Nothing, ваши уравнения будут по-прежнему давать правильный результат - без необходимости проверять на наличие Nothing.

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

В принципе, "монада" примерно означает "шаблон" . Но вместо книги, полной неофициально объясненной и специально названной Patterns, теперь у вас есть языковая конструкция - синтаксис и все, что позволяет объявлять новые шаблоны как вещи в вашей программе. (Неправильность здесь состоит в том, что все шаблоны должны следовать определенной форме, поэтому монада не такая общая, как шаблон. Но я думаю, что это самый близкий термин, который большинство людей знает и понимает.)

И вот почему люди находят монады настолько запутанными: потому что они такие общие понятия. Чтобы спросить, что делает что-то, монада так же расплывчата, что спросить, что делает что-то образцом.

Но подумайте о последствиях наличия синтаксической поддержки в языке идеи шаблона: вместо того, чтобы читать книгу Gang of Four и запоминать построение определенного шаблона, вы просто напишите код, который реализует этот шаблон агностиком, общий путь один раз, а затем вы закончите! Затем вы можете повторно использовать этот шаблон, например, "Посетитель" или "Стратегия" или "Фасад" или что-то еще, просто украсив его в своем коде, не повторяя его снова и снова!

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

+45
источник

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

Есть три вопроса, на которые нужно ответить, чтобы понять монады:

  1. Зачем тебе монада?
  2. Что такое монада?
  3. Как осуществляется монада?

Как я уже отмечал в своих первоначальных комментариях, слишком много объяснений монадов попадают под сомнение №3 без и до того, как они действительно адекватно охватывают вопрос 2 или вопрос 1.

Зачем тебе монада?

Чистые функциональные языки, такие как Haskell, отличаются от настоящих языков, таких как C или Java, что чистая функциональная программа не обязательно выполняется в определенном порядке, по одному шагу за раз. Программа Haskell более сродни математической функции, в которой вы можете решить "уравнение" в любом количестве потенциальных заказов. Это дает ряд преимуществ, среди которых заключается в том, что он устраняет возможность некоторых видов ошибок, особенно тех, которые относятся к вещам типа "государство".

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

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

consolestate MyConsole = new consolestate;

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

consolestate FinalConsole = print(input(print(myconsole, "Hello, what your name?")),"hello, %inputbuffer%!");

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

consolestate FinalConsole = myconsole:
                            print("Hello, what your name?"):
                            input():
                            print("hello, %inputbuffer%!");

Это было бы более удобным способом написать его. Как мы это делаем?

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

Когда у вас есть тип (такой как consolestate), который вы определяете вместе с кучей функций, специально предназначенных для работы с этим типом, вы можете превратить весь пакет этих вещей в "монаду", указав оператор, например : (bind) который автоматически подает возвращаемые значения слева, в функциональные параметры справа и оператор lift который превращает обычные функции, в функции, которые работают с этим конкретным типом оператора привязки.

Как осуществляется монада?

См. Другие ответы, которые кажутся совершенно свободными в деталях.

+44
источник

(См. также ответы на Что такое монада?)

Хорошей мотивацией для Монад является sigfpe (Дэн Пипони) Вы могли бы изобрести монады! (А может быть, у вас уже есть). Существует много других учебных пособий по монадам, многие из которых ошибочно пытаются объяснить монады "простыми словами", используя различные аналогии: это ошибка учебного пособия по монадам; избегайте их.

Как говорит Д.Р. Макивер в , расскажите, почему ваш язык отстой:

Итак, что я ненавижу в Хаскеле:

  Начнем с очевидного. Монады учебники. Нет, не монады. В частности, учебники. Они бесконечные, раздутые и дорогой бог, они скучны. Кроме того, я никогда не видел убедительных доказательств того, что они действительно помогают. Прочитайте определение класса, напишите некоторый код, переберите страшное имя.

Вы говорите, что понимаете монаду Может быть? Хорошо, ты уже в пути. Просто начните использовать другие монады, и рано или поздно вы поймете, что такое монады в целом.

[Если вы математически ориентированы, вы можете игнорировать десятки учебных пособий и выучить определение или следовать лекциям по теории категорий :) Основная часть определения состоит в том, что Monad M включает в себя "конструктор типов", который определяет для каждого существующего типа "T" новый тип "MT", и некоторые способы перехода назад и вперед между "обычными" типами и "M". типы.]

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

+35
источник

Монада является, фактически, формой "оператора типа". Он будет делать три вещи. Сначала он будет "обернуть" (или иным образом преобразовать) значение одного типа в другой тип (обычно называемый "монадическим типом" ). Во-вторых, он сделает все операции (или функции) доступными для базового типа доступными для монадического типа. Наконец, он обеспечит поддержку для объединения своего "я" с другой монадой для создания сложной монады.

"Возможно, монада" по сути является эквивалентом "типов с нулевым значением" в Visual Basic/С#. Он принимает невопустимый тип "T" и преобразует его в "Nullable <T> ", а затем определяет, что означают все бинарные операторы на Nullable <T> .

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

Они называются "монадами", а не просто понятными именами "операторов типов" по ​​нескольким причинам:

  • Монады имеют ограничения на то, что они могут делать (подробности см. в описании).
  • Эти ограничения, а также тот факт, что существуют три операции, соответствуют структуре чего-то, называемого монадой в Теории категорий, которая является неясной ветвью математики.
  • Они были разработаны сторонниками "чистых" функциональных языков
  • Сторонники чистых функциональных языков, таких как неясные ветки математики
  • Поскольку математика неясна, а монады связаны с определенными стилями программирования, люди склонны использовать слово монада как своего рода секретное рукопожатие. Из-за этого никто не удосужился инвестировать в лучшее имя.
+34
источник

Ответив на этот вопрос несколько лет назад, я считаю, что могу улучшить и упростить этот ответ с помощью...

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

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

(x -> y) >> (y -> z)

Эту конструкцию можно улучшить, реструктурировав входные данные, чтобы легче было запрашивать соответствующие состояния. Таким образом, вместо простого y значение может стать Mb, например, (is_OK, b), если y включает понятие достоверности.

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

(Ma -> Mb) >> (Mb -> Mc)

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

Однако, что если бы мы могли вывести усилия на допрос в те времена, когда обработка сценария является рутинной. Например, что если наша программа ничего не делает, когда ввод не в порядке, как в случае, когда is_OK равен false. Если бы это было сделано, то составным функциям не нужно было бы самим обрабатывать этот сценарий, что значительно упрощало бы их код и приводило к другому уровню повторного использования.

Для достижения этой экстернализации мы могли бы использовать функцию bind (>>=) для выполнения composition вместо compose. Таким образом, вместо простой передачи значений с выхода одной функции на вход другой Bind будет проверяться часть M в Ma и решаться, применять ли составную функцию к a и как это делать. Разумеется, функция bind будет определена специально для нашего конкретного M, чтобы иметь возможность проверять ее структуру и выполнять любые приложения, которые мы хотим. Тем не менее, a может быть чем угодно, поскольку bind просто пропускает a без проверки составной функции, когда она определяет необходимое применение. Кроме того, сами составные функции больше не должны иметь дело с частью M входной структуры, упрощая их. Следовательно...

(a -> Mb) >>= (b -> Mc) или более кратко Mb >>= (b -> Mc)

Короче говоря, монада экстернализуется и, таким образом, обеспечивает стандартное поведение при обработке определенных сценариев ввода, как только ввод становится разработанным для их достаточного раскрытия. Этот дизайн представляет собой модель shell and content, в которой оболочка содержит данные, относящиеся к применению составной функции, и запрашивается и остается доступной только для функции bind.

Следовательно, монада это три вещи:

  1. оболочка M для хранения информации, относящейся к монаде,
  2. функция bind, реализованная для использования этой информации оболочки при применении составных функций к значениям содержимого, которые она находит в оболочке, и
  3. составные функции вида a -> Mb, дающие результаты, включающие монадические данные управления.

Вообще говоря, входные данные для функции гораздо более строгие, чем ее выходные данные, которые могут включать в себя такие вещи, как условия ошибки; следовательно, структура результата Mb, как правило, очень полезна. Например, оператор деления не возвращает число, когда делителем является 0.

Кроме того, monad может включать функции переноса, которые переносят значения, a, в монадический тип, Ma, и общие функции, a -> b, в монадические функции, a -> Mb, путем переноса их результатов после применения. Конечно, как и bind, такие функции переноса являются специфическими для M. Пример:

let return a = [a]
let lift f a = return (f a)

Конструкция функции bind предполагает неизменные структуры данных и чистые функции, другие вещи усложняются и гарантии не могут быть сделаны. Таким образом, существуют монадические законы:

Принимая во внимание...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Тогда...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativity означает, что bind сохраняет порядок оценки независимо от того, когда применяется bind. То есть в приведенном выше определении Associativity принудительное раннее вычисление заключенных в скобки binding для f и g приведет только к функции, которая ожидает Ma для завершения bind. Следовательно, оценка Ma должна быть определена до того, как его значение может быть применено к f, и этот результат, в свою очередь, будет применен к g.

+34
источник

Монады должны управлять потоком, какие абстрактные типы данных относятся к данным.

Другими словами, многие разработчики довольны идеей наборов, списков, словарей (или хэшей или карт) и деревьев. Внутри этих типов данных существует много особых случаев (например, InsertionOrderPreservingIdentityHashMap).

Однако, столкнувшись с "потоком" программы, многие разработчики не подвергались воздействию многих других конструктов, чем если бы, switch/case, do, while, goto (grr) и (возможно) закрытия.

Итак, монада - это просто конструкция потока управления. Лучшей фразой для замены монады будет "тип управления".

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

Например, "if" monad:

if( clause ) then block

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

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

Конечно, точно так же, как могут быть обработаны структуры данных или пройдены, моны могут быть оценены.

Составители могут иметь или не иметь поддержки для пользовательских монадов. Хаскелл, безусловно, делает. У Ioke есть аналогичные возможности, хотя термин "монада" не используется на языке.

+24
источник

Мой любимый учебник Монады:

http://www.haskell.org/haskellwiki/All_About_Monads

(из 170 000 просмотров в поиске Google для "учебника монады"!)

@Stu: точка monads - это позволить вам добавлять (обычно) последовательную семантику в чистый код; вы даже можете составить монады (используя Monad Transformers) и получить более интересную и сложную комбинированную семантику, например, разбор с обработкой ошибок, совместное состояние и ведение журнала. Все это возможно в чистом коде, монады просто позволяют отвлечь его и повторно использовать в модульных библиотеках (всегда хорошо в программировании), а также обеспечить удобный синтаксис, чтобы он выглядел императивным.

Haskell уже имеет перегрузку оператора [1]: он использует классы типов так, как можно использовать интерфейсы в Java или С#, но Haskell просто позволяет также использовать не-буквенно-цифровые маркеры, такие как + && & и > как инфиксные идентификаторы. Это только перегрузка оператора в вашем способе взглянуть на него, если вы имеете в виду "перегрузку точки с запятой" [2]. Это звучит как черная магия и требует неприятностей "перегружать точку с запятой" (изображение предприимчивых хакеров Perl получает ветер этой идеи), но дело в том, что без monads нет точки с запятой, поскольку чисто функциональный код не требует или не допускает явное упорядочивание.

Все это звучит намного сложнее, чем нужно. статья sigfpe довольно крутая, но использует Haskell, чтобы объяснить ее, какой вид не удается сломать проблему курицы и яйца понимания Haskell, чтобы покорить Монады и понять Monads, чтобы вырвать Haskell.

[1] Это отдельная проблема из монадов, но монады используют функцию перегрузки оператора Haskell.

[2] Это также упрощение, так как оператор для цепных монадических действий → = (произносится как "bind" ), но есть синтаксический сахар ( "do" ), который позволяет использовать фигурные скобки и точки с запятой и/или отступы и новая строка.

+14
источник

Я думал о Монадах по-другому, в последнее время. Я думал о них как о абстрагировании выполнения порядка математическим способом, что делает возможными новые виды полиморфизма.

Если вы используете императивный язык, и вы пишете несколько выражений в порядке, код ALWAYS выполняется точно в этом порядке.

И в простом случае, когда вы используете монаду, она чувствует то же самое - вы определяете список выражений, которые происходят по порядку. Кроме того, в зависимости от того, какую монаду вы используете, ваш код может выполняться в порядке (например, в IO monad), параллельно по нескольким элементам одновременно (например, в Монаде списка), он может остановиться частично (например, в монаде Maybe), он может приостановиться частично, чтобы возобновиться позже (например, в монаде Возобновления), он может перемотать и начать с начала (например, в монаде Транзакции), или он может перемотать частично, чтобы попробовать другие варианты (например, в логической монаде).

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

Кроме того, в некоторых случаях можно объединить монады вместе (с монадными трансформаторами) для одновременного получения нескольких функций.

+9
источник

Я все еще новичок в монадах, но я думал, что поделюсь ссылкой, которую нашел, что мне было очень приятно читать (С ФОТОГРАФИЯМИ!!): http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/ (нет принадлежности)

В принципе, теплой и нечеткой концепцией, которую я получил из этой статьи, была концепция, согласно которой монады являются в основном адаптерами, которые позволяют разрозненные функции работать в композиционном режиме, т.е. иметь возможность создавать несколько функций, смешивать и сопоставлять их, не беспокоясь о непоследовательных типах возврата и т.д. Таким образом, функция BIND отвечает за хранение яблок с яблоками и апельсинами с апельсинами, когда мы пытаемся сделать эти адаптеры. И функция LIFT отвечает за выполнение функций "нижнего уровня" и "модернизацию" их для работы с функциями BIND и также возможность создания композиций.

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

+9
источник

В дополнение к превосходным ответам выше, позвольте мне предложить вам ссылку на следующую статью (Патрик Томсон), в которой объясняются монады, связывая концепцию с библиотекой JavaScript jQuery (и ее способ использования "цепочки методов" для манипулирования DOM): jQuery является монадой

Документация jQuery сама не относится к термину "монада", а говорит о "шаблоне строителя" "что, вероятно, более знакомо. Это не меняет того факта, что у вас есть правильная монада, возможно, даже не осознавая этого.

+8
источник

Монады - это не метафоры, а практически полезная абстракция, выходящая из общей картины, как объясняет Даниэль Спивак.

+8
источник

Монада - это способ объединения вычислений вместе, которые имеют общий контекст. Это похоже на создание сети труб. При построении сети нет данных, проходящих через него. Но когда я закончил разбивать все биты вместе с "bind" и "return", тогда я вызываю что-то вроде runMyMonad monad data, и данные проходят через трубы.

+6
источник

На практике монада - это обычная реализация оператора компоновки функций, которая заботится о побочных эффектах и ​​несовместимых значениях ввода и возврата (для цепочки).

+6
источник

Если я правильно понял, IEnumerable является производным от монад. Интересно, может ли это быть интересным углом зрения для тех из нас, кто живет в мире С#?

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

+5
источник

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

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

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

+5
источник

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

В то время как деление принимает две дробные числа и возвращает дробное число, которое определило деление на ноль как Бесконечность в haskell somehy (что иногда бывает дробным)...

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

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

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

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

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

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

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

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

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

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

+5
источник

В контексте Scala вы найдете следующее простейшим определением. В основном flatMap (или bind) является "ассоциативным" и существует тождество.

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)

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

+5
источник

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

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

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) как "feed 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, то ваша пара является значением типа "пара t и строка". Вызовите этот тип M t.

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

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

Формально ваша монада является кортежем <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
    • feeds 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.

+5
источник

Я попытаюсь объяснить 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 type определяет оператор >>=, который позволяет передавать значения в контексте (мы называем эти монадические значения) функциям, которые не ожидают значений в контексте. Контекст будет рассмотрен.

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

+5
источник

ТЛ; др

{-# 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 $

с точки зрения применения функции Haskell-примитива fx (infixl 10).

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

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

infixr 9 .

и удовлетворяет эквивалентности для forall fg 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

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

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

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

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

Монады канонически определены в терминах тройного поля Клейсли forall m. Functor m => 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. Приложение Extension или Kleisli =<< применяет стрелку Kleisli a → mb к результатам вычисления ma.

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

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

infixr 1 <=<

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

Экземпляры класса монадного типа должны подчиняться законам монады, наиболее элегантно изложенным в терминах композиции Клейсли: forall fg 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] полученные в результате применения fx стрелки Клейсли 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   =   []

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

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

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

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 kl 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 пропускается.

государственный

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

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

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

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

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

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

Монада процессора состояний - это функтор State 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 mf 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

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

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

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

от обобщающих монад до стрел Джон Хьюз

+5
источник

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

+4
источник

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

Sierpinski triangle

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

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

+4
источник

Монада - это вещь, используемая для инкапсуляции объектов, имеющих изменяющееся состояние. Это чаще всего встречается на языках, которые в противном случае не позволяют вам иметь изменяемое состояние (например, Haskell).

Пример для ввода/вывода файлов.

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

+3
источник

Пусть ниже "{| 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}

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

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

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

(Ok, here 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), и если мы хотим работать с рекламируемым значением, у нас мало выбора, кроме как работать с ним монадическим интерфейсом.

+3
источник
  • 1
  • 2

Посмотрите другие вопросы по меткам или Задайте вопрос