Comments 11
Для кого все три статьи остались неясны, монады проще понять «на пальцах».
Пусть есть некоторый алгебраический тип, к примеру:
Мы знаем из чего состоит тип, значит можем на прямую рассмотреть case value of с альтернативами для Just a и Nothing. Исходя из этих знаний, мы можем сделать функцию
А теперь представим что есть некий алгебраический тип, о котором мы ничего не знаем, только лишь конструктор. Значит
Раз мы не знаем что есть в
Заметим, что зная тонкости внутренней реализации ввода/вывода (или любой сторонней либы, чего угодно), можно написать к примеру
Пусть есть некоторый алгебраический тип, к примеру:
data Maybe a = Nothing | Just a
Мы знаем из чего состоит тип, значит можем на прямую рассмотреть case value of с альтернативами для Just a и Nothing. Исходя из этих знаний, мы можем сделать функцию
f::Maybe a -> a
, выдав значение по умолчанию для Nothing. Это чистые значения.А теперь представим что есть некий алгебраический тип, о котором мы ничего не знаем, только лишь конструктор. Значит
f::IO String
действительно может быть какой угодно альтернативой в IO
, о которой мы ничего не знаем. Это может быть прочтенная строка с консоли, ошибка, что угодно. Заметим, что мы даже не можем предполагать, что значение с типом IO String
это действительно какая то альтернатива IO, нет это всегда одно значение, но оно «многолико». Именно поэтому getLine::IO String
это значение (функция без аргументов), которое может «прочесть любую строку с консоли» — прочтенная строка будет строкой/ошибкой/etc для наблюдателя (наблюдатель не находиться в «чистом» мире), в самой программе оперируется чистое значение «все возможные варианты IO» (ключевые: суперпозиция, посыл к квантовой физике)Раз мы не знаем что есть в
IO
или в любом другом «неизвестном типе»/монаде, значит нельзя что либо сделать со значением IO a
. Для этого и есть оператор применения >>=, который достает а из монадного значения и применяет ее к функции f::a -> IO a
если это возможно. Иначе (если к примеру IO String
был ошибкой, а не прочитанной строкой) возвращает новое значение IO a, о котором мы ничего не знаем. Именно потому, что >>= может вернуть IO a
«в обход» нашей f, f должна возвращать IO a
. Т.е. однажды «испачкавшись» в монадах вы увязли до самого конца программы. В функциональной программе нет времени, т.е. нельзя стать наблюдателем и посмотреть что там в монадном значении (unsafe функции, те которые при «исключительных» значениях «не знают что делать» и оставлены на совесть программисту, мы не рассматриваем. это хак малодушных :), это не Haskell), значит нельзя избавиться от монада. На самом деле программа на Haskell всегда, не зависимо от времени, окружающего мира и т.д. отдает IO ()
, «многоликое» неизвестное значение, и завершается, мгновенно. После работы программы мы в своем мире с side-effects наблюдаем за полученным значением с типом IO ()
и видим один из «выпавших» вариантов: вывод на консоль, окошки и т.д. Даже если программа работает несколько суток, общается с пользователем и выкачивает чего из интернета, все равно в «чистом мире» Haskell программа уже отработала и вернула нам IO ()
со всем что мы видим, можем ввести и можем получить результат обратно. Там нет времени, там нет side-effects :)Заметим, что зная тонкости внутренней реализации ввода/вывода (или любой сторонней либы, чего угодно), можно написать к примеру
hasFailed::IO а -> Bool
. Но это совсем не хорошо, т.к. из всего «бесконечного» множества вариантов IO a
мы вдруг получили Bool, который всегда True | False, не зависимо от действий внешнего мира и времени. Таких функций быть не должно, это unsafe функции. Но может быть hIsClosed::Handle -> IO Bool
, применяемая к IO Handle
. Тогда логика скрыта в Handle, если там не что-то еще из бесконечного множество «чего то еще». Получил IO Bool
мы можем сделать свою логику f::Bool -> IO SomethingElse
или дальше катиться в потоке IO SomethingElse. Это может сносить немного крышу. Что бы это понять попробуйте представить, что ваша программа может дать верные результаты или выпасть с ошибками — это все «известные» результаты; либо уборщица шваброй выдернет питание и все накроется — это «не известные» результаты, одно из не подвластных вам проявлений IO SomethingElse
. Функциональный мир при этом остается чист и детерминирован.+4
только мне эти игры с монадами напоминают запихивание мусора под диван. типа у нас абсолютно стерильная комната, но она бесполезна, ибо в ней ничего нет. а если вам что-то нужно — извольте испачкаться в грязи по самые уши, забравшись под диван. и в нём вы останетесь навсегда, изредка высовывая нос подышать чистым воздухом, чтобы потом обратно окунуться в недетерминированный мир.
+2
Это не так. Весь код на Haskell всегда детерминирован и чист (в рамках логики). К примеру
в принципе без выполнения, даже на этапе написания кода, всегда будет возвращать значение типа IO (). Детерминизм, type-safety, верификация, все присутствует и никаких поблажек. А потом пользователь перенесет получившееся значение, в свой «не чистый» мир, глянув в него. И тут открылись файлы, прочитались строки, заработали SOAP соединения со всеми случайными сбоями и т.д. «Чистая» программа к тому моменту уже «отработала», выдав одно из бесконечно возможных значений типа IO (). А значение — это один из бесконечно возможных сценариев работы императивной программы со всем вводом и выводом. С некоторыми оговорками можно сказать, что переход из «чистого» мира происходит когда Haskell программа транслируется в C/машинный код (императивный, шаг за шагом код), результат которого наоборот зависит от окружающей обстановки. А монады (типы, о которых мы можем ничего не знать, кроме определенных операций >>=, return и т.д.), позволяют завернуть понятие «все возможные значения/сценарии Х» в некоторое не известное значение монадного типа.
Не все монады «не известны». Maybe тоже монад (тут важно наличие конструктора типов, операций >>=, return, fail etc) со вполне известными вариантами. Просто монады, помимо других задач, элегантно решают проблемы не детерминированного мира (позволяют не знать о себе ничего).
getLine::IO String
это всегда одно и то же значение типа IO String
, не зависимо от времени или каких либо других сторонних факторов. putStr::String -> IO()
всегда возвращает одно и тоже значение типа IO ()
для одной и той же строки. Выражение:(getLine >>= putStr)::IO ()
в принципе без выполнения, даже на этапе написания кода, всегда будет возвращать значение типа IO (). Детерминизм, type-safety, верификация, все присутствует и никаких поблажек. А потом пользователь перенесет получившееся значение, в свой «не чистый» мир, глянув в него. И тут открылись файлы, прочитались строки, заработали SOAP соединения со всеми случайными сбоями и т.д. «Чистая» программа к тому моменту уже «отработала», выдав одно из бесконечно возможных значений типа IO (). А значение — это один из бесконечно возможных сценариев работы императивной программы со всем вводом и выводом. С некоторыми оговорками можно сказать, что переход из «чистого» мира происходит когда Haskell программа транслируется в C/машинный код (императивный, шаг за шагом код), результат которого наоборот зависит от окружающей обстановки. А монады (типы, о которых мы можем ничего не знать, кроме определенных операций >>=, return и т.д.), позволяют завернуть понятие «все возможные значения/сценарии Х» в некоторое не известное значение монадного типа.
Не все монады «не известны». Maybe тоже монад (тут важно наличие конструктора типов, операций >>=, return, fail etc) со вполне известными вариантами. Просто монады, помимо других задач, элегантно решают проблемы не детерминированного мира (позволяют не знать о себе ничего).
+3
Фортрановскую программу можно написать на любом языке, но вы заблуждаетесь, если думаете, что это обязательно.
+1
На самом деле, ваше объяснение тоже раскрывает не все. Монады — мир огромный, да… И пока их руками не потрогаешь, не поймешь.
А IO вообще интересная монада. Если ее правильно использовать, она делит код на слои, что есть хорошо.
А IO вообще интересная монада. Если ее правильно использовать, она делит код на слои, что есть хорошо.
+1
Жду следующей части. Я пока не знаю haskell, но интересуюсь функциональным программированием в целом. Очень хороший, интересный, понятный цикл даже для начинающих, таких как я. Монады — одна из наиболее любопытных для меня концепций. Я пытался с разных сторон подходить к изучению этой концепции, и со стороны теории категорий, и с практической стороны, но наиболее ясное представление о том, что же это такое и зачем нужно дали эти переводы :)
+2
Взгляд питониста: пуристы страдают, но никогда в этом не признаются. Лишь поют мантры о чистоте, верифицируемости, детерминизме. Но реальный мир прёт из всех щелей, а абстракции текут
+1
Я не совсем понимаю, в каком таком негативном ключе вы отзываетесь о чистоте, верифицируемости и детерминизме.
0
Взгляд со стороны — питонисты занимаются мазохизмом, покрывая юнит-тестами малую часть ошибок, легко обнаруживаемых компилятором хаскеля.
+1
Sign up to leave a comment.
Еще Одно Руководство по Монадам (часть 3: Монадные Законы)