Функциональное программирование: где на самом деле происходит побочный эффект?
После того, как я начал изучать Haskell, в Haskell есть что-то, чего я не понимаю, даже после прочтения большого количества документации.
Я понимаю, что для выполнения операций ввода-вывода вы должны использовать "монаду ввода-вывода", которая заключает значение в своего рода "черный ящик", поэтому любая функция, использующая монаду ввода-вывода, все еще является чисто функциональной. Хорошо, хорошо, но тогда где же происходит операция ввода-вывода?
Значит ли это, что сама монада не является чисто функциональной? Или операция ввода-вывода реализована, скажем, в C, и "встроена" в компилятор Haskell?
Могу ли я написать на чистом Хаскеле, с монадой или без нее, что-нибудь, что будет выполнять операции ввода-вывода? И если нет, откуда эта способность, если она невозможна внутри самого языка? Если он встроен / связан в компиляторе Haskell с частями кода C, будет ли он в конечном счете вызываться монадой ввода-вывода для выполнения "грязной работы"?
1 ответ
В качестве предисловия это не " IO
Monad
", несмотря на то, что говорят многие плохо написанные введения. Это просто" IO
типа ". Нет ничего волшебного в монаде. Хаскелла Monad
класс - довольно скучная вещь - он просто незнаком и более абстрактен, чем то, что поддерживает большинство языков. Вы никогда не видите, чтобы кто-нибудь позвонил IO
" IO
Alternative
," даже если IO
инвентарь Alternative
, Слишком много внимания уделяется Monad
только мешает учиться.
Концептуальной магией для принципиальной обработки эффектов (не побочных эффектов!) В чистых языках является существование IO
типа вообще. Это настоящий тип. Это не какой-то флаг, говорящий "Это нечисто!". Это полный тип Хаскеля * -> *
, как Maybe
или же []
, Сигнатуры типа, принимающие значения ввода-вывода в качестве аргументов, например IO a -> IO (Maybe a)
, имеет смысл. Тип подписи с вложенным IO имеет смысл, как IO (IO a)
,
Так что если это реальный тип, он должен иметь конкретное значение. Maybe a
как тип представляет возможно пропущенное значение типа a
, [a]
означает 0 или более значений типа a
, IO a
означает последовательность эффектов, которые производят значение типа a
,
Обратите внимание, что вся цель IO
должен представлять последовательность эффектов. Как я уже говорил выше, они не являются побочными эффектами. Они не могут быть спрятаны в безобидно выглядящем листе программы и таинственным образом изменить вещи за спиной другого кода. Вместо этого эффекты довольно явно вызваны тем фактом, что они IO
значение. Вот почему люди пытаются минимизировать часть своей программы, используя IO
типы. Чем меньше вы там занимаетесь, тем меньше возможностей для жутких действий на расстоянии мешать вашей программе.
Что касается основной направленности вашего вопроса, то - полная программа на Haskell является IO
значение называется main
и набор определений, которые он использует. Компилятор, когда он генерирует код, вставляет явно не-Haskell блок кода, который фактически выполняет последовательность эффектов в IO
значение. В некотором смысле это то, к чему добивался Саймон Пейтон Джонс (один из давних авторов GHC) в своем выступлении, Хаскелл бесполезен.
Это правда, что то, что фактически выполняет IO-действие, не может оставаться концептуально чистым. (И есть та самая нечистая функция, которая работает IO
действия, представленные на языке Haskell. Я не буду говорить об этом больше, чем это было добавлено для поддержки интерфейса сторонней функции, и неправильное его использование очень сильно нарушит вашу программу.) Но цель Haskell в том, чтобы предоставить принципиальный интерфейс для системы эффектов и спрятаться беспринципные биты. И делает это таким образом, что это на самом деле весьма полезно на практике.