Вопрос: Что такое монада?


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

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


1218


источник


Ответы:


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

Вы запрашиваете практические примеры:

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

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

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

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

do
   x <- [1..10]
   if odd x 
       then [x * 2] 
       else []

Или даже:

[1..10] >>= (\x -> if odd x then [x*2] else [])

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

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

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

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

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

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

Пример 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- он фактически ожидает ответа в отдельном потоке, в то время как основной поток возвращается из функции. Последние три строки выполняются в порожденном потоке, когда ответ получен.

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

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

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

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

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

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

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

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

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

Подведение итогов

В терминах Хаскелла монада является параметризованным типом, который является экземпляром класса типа Монады, который определяет >>=наряду с несколькими другими операторами. В условиях неспециалиста монада - это всего лишь тип, для которого >>=операция определена.

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

Почему монады крепкие?

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

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


960



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

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

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

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

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

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

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

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

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

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

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

  • Помимо передачи «результата» с одного шага на другой, вы можете иметь функцию связывания передавать дополнительные данные вокруг. Эти данные теперь не отображаются в исходном коде, но вы все равно можете получить доступ к нему из любого места, без необходимости вручную передавать его каждой функции. (The Читатель Монада .)

  • Вы можете сделать так, чтобы «дополнительные данные» можно было заменить. Это позволяет имитировать деструктивные обновления , без фактических деструктивных обновлений. (The Государственная Монада и его двоюродный брат Писатель Монада .)

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

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

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

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


635



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

Например, вы можете создать тип для переноса другого, в 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операции.

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

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

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


165



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

Сигфп говорит:

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

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

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


162



Монада - это тип данных, который имеет две операции: >>=(ака bind) а также return(ака 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, чтобы оценить, что такое монады и почему они полезны.


78



You should first understand what a functor is. Before that, understand higher-order functions.

A higher-order function is simply a function that takes a function as an argument.

A functor is any type construction T for which there exists a higher-order function, call it map, that transforms a function of type a -> b (given any two types a and b) into a function T a -> T b. This map function must also obey the laws of identity and composition such that the following expressions return true for all x, p, and q (Haskell notation):

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

For example, a type constructor called List is a functor if it comes equipped with a function of type (a -> b) -> List a -> List b which obeys the laws above. The only practical implementation is obvious. The resulting List a -> List b function iterates over the given list, calling the (a -> b) function for each element, and returns the list of the results.

A monad is essentially just a functor T with two extra methods, join, of type T (T a) -> T a, and unit (sometimes called return, fork, or pure) of type a -> T a. For lists in Haskell:

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

Why is that useful? Because you could, for example, map over a list with a function that returns a list. Join takes the resulting list of lists and concatenates them. List is a monad because this is possible.

You can write a function that does map, then join. This function is called bind, or flatMap, or (>>=), or (=<<). This is normally how a monad instance is given in Haskell.

A monad has to satisfy certain laws, namely that join must be associative. This means that if you have a value x of type [[[a]]] then join (join x) should equal join (map join x). And pure must be an identity for join such that join (pure x) == x.


69



[Disclaimer: I am still trying to fully grok monads. The following is just what I have understood so far. If it’s wrong, hopefully someone knowledgeable will call me on the carpet.]

Arnar wrote:

Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it.

That’s precisely it. The idea goes like this:

  1. You take some kind of value and wrap it with some additional information. Just like the value is of a certain kind (eg. an integer or a string), so the additional information is of a certain kind.

    E.g., that extra information might be a Maybe or an IO.

  2. Then you have some operators that allow you to operate on the wrapped data while carrying along that additional information. These operators use the additional information to decide how to change the behaviour of the operation on the wrapped value.

    E.g., a Maybe Int can be a Just Int or Nothing. Now, if you add a Maybe Int to a Maybe Int, the operator will check to see if they are both Just Ints inside, and if so, will unwrap the Ints, pass them the addition operator, re-wrap the resulting Int into a new Just Int (which is a valid Maybe Int), and thus return a Maybe Int. But if one of them was a Nothing inside, this operator will just immediately return Nothing, which again is a valid Maybe Int. That way, you can pretend that your Maybe Ints are just normal numbers and perform regular math on them. If you were to get a Nothing, your equations will still produce the right result – without you having to litter checks for Nothing everywhere.

But the example is just what happens for Maybe. If the extra information was an IO, then that special operator defined for IOs would be called instead, and it could do something totally different before performing the addition. (OK, adding two IO Ints together is probably nonsensical – I’m not sure yet.) (Also, if you paid attention to the Maybe example, you have noticed that “wrapping a value with extra stuff” is not always correct. But it’s hard to be exact, correct and precise without being inscrutable.)

Basically, “monad” roughly means “pattern”. But instead of a book full of informally explained and specifically named Patterns, you now have a language construct – syntax and all – that allows you to declare new patterns as things in your program. (The imprecision here is all the patterns have to follow a particular form, so a monad is not quite as generic as a pattern. But I think that’s the closest term that most people know and understand.)

And that is why people find monads so confusing: because they are such a generic concept. To ask what makes something a monad is similarly vague as to ask what makes something a pattern.

But think of the implications of having syntactic support in the language for the idea of a pattern: instead of having to read the Gang of Four book and memorise the construction of a particular pattern, you just write code that implements this pattern in an agnostic, generic way once and then you are done! You can then reuse this pattern, like Visitor or Strategy or Façade or whatever, just by decorating the operations in your code with it, without having to re-implement it over and over!

So that is why people who understand monads find them so useful: it’s not some ivory tower concept that intellectual snobs pride themselves on understanding (OK, that too of course, teehee), but actually makes code simpler.


43



After much striving, I think I finally understand the monad. After rereading my own lengthy critique of the overwhelmingly top voted answer, I will offer this explanation.

There are three questions that need to be answered to understand monads:

  1. Why do you need a monad?
  2. What is a monad?
  3. How is a monad implemented?

As I noted in my original comments, too many monad explanations get caught up in question number 3, without, and before really adequately covering question 2, or question 1.

Why do you need a monad?

Pure functional languages like Haskell are different from imperative languages like C, or Java in that, a pure functional program is not necessarily executed in a specific order, one step at a time. A Haskell program is more akin to a mathematical function, in which you may solve the "equation" in any number of potential orders. This confers a number of benefits, among which is that it eliminates the possibility of certain kinds of bugs, particularly those relating to things like "state".

However, there are certain problems that are not so straightforward to solve with this style of programming. Some things, like console programming, and file i/o, need things to happen in a particular order, or need to maintain state. One way to deal with this problem is to create a kind of object that represents the state of a computation, and a series of functions that take a state object as input, and return a new modified state object.

So let's create a hypothetical "state" value, that represents the state of a console screen. exactly how this value is constructed is not important, but let's say it's an array of byte length ascii characters that represents what is currently visible on the screen, and an array that represents the last line of input entered by the user, in pseudocode. We've defined some functions that take console state, modify it, and return a new console state.

consolestate MyConsole = new consolestate;

So to do console programming, but in a pure functional manner, you would need to nest a lot of function calls inside eachother.

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

Programming in this way keeps the "pure" functional style, while forcing changes to the console to happen in a particular order. But, we'll probably want to do more than just a few operations at a time like in the above example. Nesting functions in that way will start to become ungainly. What we want, is code that does essentially the same thing as above, but is written a bit more like this:

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

This would indeed be a more convenient way to write it. How do we do that though?

What is a monad?

Once you have a type (such as consolestate) that you define along with a bunch of functions designed specifically to operate on that type, you can turn the whole package of these things into a "monad" by defining an operator like : (bind) that automatically feeds return values on its left, into function parameters on its right, and a lift operator that turns normal functions, into functions that work with that specific kind of bind operator.

How is a monad implemented?

See other answers, that seem quite free to jump into the details of that.


37



(See also the answers at What is a monad?)

A good motivation to Monads is sigfpe (Dan Piponi)'s You Could Have Invented Monads! (And Maybe You Already Have). There are a LOT of other monad tutorials, many of which misguidedly try to explain monads in "simple terms" using various analogies: this is the monad tutorial fallacy; avoid them.

As DR MacIver says in Tell us why your language sucks:

So, things I hate about Haskell:

Let’s start with the obvious. Monad tutorials. No, not monads. Specifically the tutorials. They’re endless, overblown and dear god are they tedious. Further, I’ve never seen any convincing evidence that they actually help. Read the class definition, write some code, get over the scary name.

You say you understand the Maybe monad? Good, you're on your way. Just start using other monads and sooner or later you'll understand what monads are in general.

[If you are mathematically oriented, you might want to ignore the dozens of tutorials and learn the definition, or follow lectures in category theory :) The main part of the definition is that a Monad M involves a "type constructor" that defines for each existing type "T" a new type "M T", and some ways for going back and forth between "regular" types and "M" types.]

Also, surprisingly enough, one of the best introductions to monads is actually one of the early academic papers introducing monads, Philip Wadler's Monads for functional programming. It actually has practical, non-trivial motivating examples, unlike many of the artificial tutorials out there.


34



A monad is, effectively, a form of "type operator". It will do three things. First it will "wrap" (or otherwise convert) a value of one type into another type (typically called a "monadic type"). Secondly it will make all the operations (or functions) available on the underlying type available on the monadic type. Finally it will provide support for combining its self with another monad to produce a composite monad.

The "maybe monad" is essentially the equivalent of "nullable types" in Visual Basic / C#. It takes a non nullable type "T" and converts it into a "Nullable<T>", and then defines what all the binary operators mean on a Nullable<T>.

Side effects are represented simillarly. A structure is created that holds descriptions of side effects alongside a function's return value. The "lifted" operations then copy around side effects as values are passed between functions.

They are called "monads" rather than the easier-to-grasp name of "type operators" for several reasons:

  1. Monads have restrictions on what they can do (see the definiton for details).
  2. Those restrictions, along with the fact that there are three operations involved, conform to the structure of something called a monad in Category Theory, which is an obscure branch of mathematics.
  3. They were designed by proponents of "pure" functional languages
  4. Proponents of pure functional languages like obscure branches of mathematics
  5. Because the math is obscure, and monads are associated with particular styles of programming, people tend to use the word monad as a sort of secret handshake. Because of this no one has bothered to invest in a better name.

30



I wrote this mostly for me but I hope others find it useful :)

In Summary

A monad is merely custom function composition utilizing a wrapping type to hold stateful/meta information that guides the composition. Interestingly, imperative execution is itself function composition wherein the wrapping type holds whether an exception has occurred, the presence of which aborts further composition. The monadic laws serve to ensure a monad only performs the equivalent of function composition over the wrapped value.

A Little Context

Imperative code is a sequence of instructions performed consecutively while functional code is actually an expression whose order of evaluation is ultimately on an as needed basis. Consequently, an imperative program evaluates each statement fully before proceeding to the next one while a functional program catalogs any preceding subexpression then evaluates the last one to generate a result, at which point any necessary preceding subexpressions are evaluated. (Most functional languages do resolve subexpressions into values as they are encountered with a few, notably Haskell, being the exception).

What Does That Have To Do With Monads

It turns out that imperative evaluation and "expression" evaluation are equivalent. Namely, the performance of an evaluation of an expression at any specific time can be described as the sequential evaluation of its subexpressions. Similarly, any sequential execution of statements can be considered an instance of such an expression being evaluated especially if you generalize the notion of return value to be any modification to the application state as oppose to merely formal return values. To the point, all imperative code can be considered a specific ordered evaluation of some equivalent expression (over the application state).

Still, What Does All That Have To Do With Monads

An expression is a composition of functions and a monad is a technique for customizing the composition of functions. Per convention, it is a function called, bind, that composes (binds) two functions (bind f g = (x) => g(f x)) within some umbrella type that holds a context or state guiding the binding process. This context allows the associated implementation of bind to massage the composition. In the case of, Result 'a, context that hold an error message or a actual value it would permit the bind to log errors of the preceding result and aborts or continue the composition with a default value, depending on design. Selective application of the subsequent function to be composed is the key facility of monads in that the application process is not blind, unlike silly imperative code. :)

In a monad, the functions to be composed are defined to receive a non-monadic or unwrapped value but return a monadic or wrapped value. An example of one such naturally occurring function is the division operator (/) which accepts numbers but may return undefined if the divisor is 0. Since undefined is not a number, \, actually returns a type whose value range is large than a number since it includes all numbers plus undefined.

I suspect this asymmetry, where operators require valid values but may return invalid ones, is a key pragmatic aspect of monads that make them useful but difficult to explain and justify; while imperative coders accept constant result checking as a necessary evil, functional coders use bind over a suitable wrapper type to detect and manage exceptions or invalid results

While being a composition function, nonetheless, for pragmatic reasons bind is described as receiving the result of the first function, a wrapped value, that it then selectively applies to the second function whose result it then collects and returns as its result. Consequently, binds can themselves be composed.

For a complex example, a monad can take a sequence of asynchronous results as a preceding value and apply each to the provided composing function in parallel with respect to the available processors, then collect and return their results as they occur, for instance. All this functionality is in that specific implementation of that monad's bind function; however, there are typically ancillary functions that cooperate, in particular lift with wraps a value into a default instance of the monadic type such as, lift 1 = Just 1 in the case of the Maybe monad, for participation in monadic expressions. (In Haskell, a monad is strongly associated with the wrapper type because monads are implemented using its type class mechanisms whereas in F# the implementation uses defined computations expression instances)

Are you saying that...

...a monad is just custom function composition using a wrapping type to hold stateful information that the bind or composing function or operator uses in determining whether and how to perform the application of the next function. Yep.

What About The Monadic Laws?

The monadic laws only seek to ensure that a monad implementation only performs the equivalent of function composition over the wrapped type. It does that by ensuring certain equivalences hold per the expectation of composing those two functions over the wrapped type.

That's it I think. Hope it helps some.

I believe this is nice a nice treatment in another StackOverflow of response.


28