Pull to refresh

Comments 11

Для кого все три статьи остались неясны, монады проще понять «на пальцах».

Пусть есть некоторый алгебраический тип, к примеру:

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. Функциональный мир при этом остается чист и детерминирован.
только мне эти игры с монадами напоминают запихивание мусора под диван. типа у нас абсолютно стерильная комната, но она бесполезна, ибо в ней ничего нет. а если вам что-то нужно — извольте испачкаться в грязи по самые уши, забравшись под диван. и в нём вы останетесь навсегда, изредка высовывая нос подышать чистым воздухом, чтобы потом обратно окунуться в недетерминированный мир.
Это не так. Весь код на Haskell всегда детерминирован и чист (в рамках логики). К примеру getLine::IO String это всегда одно и то же значение типа IO String, не зависимо от времени или каких либо других сторонних факторов. putStr::String -> IO() всегда возвращает одно и тоже значение типа IO () для одной и той же строки. Выражение:

(getLine >>= putStr)::IO ()

в принципе без выполнения, даже на этапе написания кода, всегда будет возвращать значение типа IO (). Детерминизм, type-safety, верификация, все присутствует и никаких поблажек. А потом пользователь перенесет получившееся значение, в свой «не чистый» мир, глянув в него. И тут открылись файлы, прочитались строки, заработали SOAP соединения со всеми случайными сбоями и т.д. «Чистая» программа к тому моменту уже «отработала», выдав одно из бесконечно возможных значений типа IO (). А значение — это один из бесконечно возможных сценариев работы императивной программы со всем вводом и выводом. С некоторыми оговорками можно сказать, что переход из «чистого» мира происходит когда Haskell программа транслируется в C/машинный код (императивный, шаг за шагом код), результат которого наоборот зависит от окружающей обстановки. А монады (типы, о которых мы можем ничего не знать, кроме определенных операций >>=, return и т.д.), позволяют завернуть понятие «все возможные значения/сценарии Х» в некоторое не известное значение монадного типа.

Не все монады «не известны». Maybe тоже монад (тут важно наличие конструктора типов, операций >>=, return, fail etc) со вполне известными вариантами. Просто монады, помимо других задач, элегантно решают проблемы не детерминированного мира (позволяют не знать о себе ничего).
Фортрановскую программу можно написать на любом языке, но вы заблуждаетесь, если думаете, что это обязательно.
На самом деле, ваше объяснение тоже раскрывает не все. Монады — мир огромный, да… И пока их руками не потрогаешь, не поймешь.

А IO вообще интересная монада. Если ее правильно использовать, она делит код на слои, что есть хорошо.
Жду следующей части. Я пока не знаю haskell, но интересуюсь функциональным программированием в целом. Очень хороший, интересный, понятный цикл даже для начинающих, таких как я. Монады — одна из наиболее любопытных для меня концепций. Я пытался с разных сторон подходить к изучению этой концепции, и со стороны теории категорий, и с практической стороны, но наиболее ясное представление о том, что же это такое и зачем нужно дали эти переводы :)
Взгляд питониста: пуристы страдают, но никогда в этом не признаются. Лишь поют мантры о чистоте, верифицируемости, детерминизме. Но реальный мир прёт из всех щелей, а абстракции текут
Я не совсем понимаю, в каком таком негативном ключе вы отзываетесь о чистоте, верифицируемости и детерминизме.
никакого негатива, выше tenshi уже сформулировал «ощущения»
Взгляд со стороны — питонисты занимаются мазохизмом, покрывая юнит-тестами малую часть ошибок, легко обнаруживаемых компилятором хаскеля.
Sign up to leave a comment.

Articles