Как стать автором
Обновить

Комментарии 4

Гениальная статья! Все понял и чувствую себя почти что просвещенным :) Большое спасибо за перевод.

Скажите, а верна ли моя догадка, что IO на самом деле является внутренней монадой компилятора (подобно встроенным типам, таким, как Int), и когда main возвращает эту монаду, как бы «за кадром» начинается выполнение программы? То есть ни в каком Prelude не объявлен соответствующий экземпляр класса Monad.
У меня есть подозрение, что Prelude вообще лишь «агрегатор» многих функций из многих модулей. Что касается монады IO, то в литературе, которая мне встречалась, говорилось, что IO ничем от других монад не отличается, за исключением того что его конструктор (собственно, «IO») не экспортируется из модуля System.IO, — и таким нехитрым образом (через цепочку следствий) нельзя написать функцию с чистым типом, у которой внутри вызывались бы функции из монады IO.

Однако, ручаться не буду, потому что в реальности оно обычно не так, как на самом деле. :) Для уточнения этого вопроса я, пожалуй, загляну накануне в исходники какого-нибудь компилятора (ну, GHC, скорее всего). Точно знаю: «зашитые» в язык вещи есть. Например, конструктор списков (квадратные скобки — []). Ничто не мешает и монаде IO быть каким-то образом «зашитой» там же.
У каждой монады свой способ «распаковать» монадическое значение

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

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

Аналогично с IO — распаковка в смысле extract должна была бы осуществлять действия, связанные с вводом-выводом. Но мы-то знаем, что при >>= никаких действий ввода-вывода не осуществляется.

Если Вы имели в виду какой-то другой смысл слова «распаковка», то стоит это пояснить.

Я бы использовал другое объяснение — >>= производит применения функции к значению внутри монады, не производя распаковки, а затем получающееся «дважды монадическое» значение некоторым специфичным для монады образом (известным, как функция join) перепаковывает в просто монадическое.

На примере списка — монадическая функция применяется к каждому элементу списка, затем получившийся список списков делается плоским.
Я буду рад перевести ваше сообщение и отправить его автору. :)

Вообще-то, конечно, тонкости здесь есть. Мы можем собрать последовательность действий при помощи bind или do-нотации и сохранить их в соответствующей структуре до будущего выполнения. В этом случае, действительно, никакой «распаковки» не происходит. Но когда дойдет до выполнения, оператор >>= в монаде IO все-таки занимается «распаковкой». Он берет значение типа IO a, достает из него значение a (все так же находясь в монаде IO) и передает его в следующую функцию. Не стоит путать «выполнение» и «распаковку»: при ленивом порядке вычислений выполнение может начаться не в том же участке кода, где оно написано. То есть, «выполнение» — это реальные действия, которые печатают что-то в консоли, берут из консоли, запускают генератор случайных чисел и так далее, а «распаковка» — это указание, что когда будем выполнять эти инструкции, то должны извлечь вот эти данные. «Распаковка» декларирует действие для будущего выполнения, но не запускает его тут же, немедленно.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации