Это предполагаемая «интерпретация» IO
монады. Если вы хотите отнестись к этой «интерпретации» серьезно, то вам нужно отнестись к «RealWorld» серьезно. Не имеет значения, action world
будет ли он спекулятивно оценен или нет, action
не имеет побочных эффектов, его эффекты, если таковые имеются, обрабатываются путем возврата нового состояния юниверса, в котором эти эффекты имели место, например, был отправлен сетевой пакет. Тем не менее, результатом функции является ((),world)
и, следовательно, новое состояние вселенной world
. Мы не используем новую вселенную, которую мы могли спекулятивно оценить на стороне. Состояние вселенной есть world
.
Вам, наверное, тяжело воспринимать это всерьез. Есть много способов, как это в лучшем случае внешне парадоксально и бессмысленно. Параллельность особенно неочевидна или сумасшедшая с этой точки зрения.
«Подожди, подожди», - говорите вы. « RealWorld
это просто« знак ». На самом деле это не состояние всей вселенной». Хорошо, тогда эта «интерпретация» ничего не объясняет. Тем не менее, как деталь реализации , именно так моделируется GHC IO
. 1 Однако это означает, что у нас есть магические «функции», которые на самом деле имеют побочные эффекты, и эта модель не дает указаний по их значению. И, поскольку у этих функций действительно есть побочные эффекты, проблема, о которой вы говорите, полностью соответствует действительности. GHC действительно должен выйти из своего пути , чтобы убедиться , что RealWorld
и эти специальные функции не оптимизированы таким образом , чтобы изменить целевое поведение программы.
Лично (как это, вероятно, очевидно сейчас), я думаю, что эта «проходящая мимо» модель IO
просто бесполезна и запутана как педагогический инструмент. (Полезно ли это для реализации, я не знаю. Для GHC я думаю, что это скорее исторический артефакт.)
Один альтернативный подход заключается в том, чтобы рассматривать IO
в качестве описания запросы с обработчиками ответов. Есть несколько способов сделать это. Вероятно, наиболее доступным является использование свободной конструкции монады, в частности, мы можем использовать:
data IO a = Return a | Request OSRequest (OSResponse -> IO a)
Есть много способов сделать это более изощренным и иметь несколько лучшие свойства, но это уже улучшение. Это не требует глубоких философских предположений о природе реальности, чтобы понять. Все, что он заявляет - это IO
либо тривиальная программа Return
, которая ничего не делает, но возвращает значение, либо это запрос к операционной системе с обработчиком ответа. OSRequest
может быть что-то вроде:
data OSRequest = OpenFile FilePath | PutStr String | ...
Точно так же OSResponse
может быть что-то вроде:
data OSResponse = Errno Int | OpenSucceeded Handle | ...
(Одно из усовершенствований , которые можно сделать, чтобы сделать вещи более типобезопасно так , что вы знаете , что вы не получите OpenSucceeded
от PutStr
запроса.) Эти модели , IO
как описывающие запросов , которые интерпретируются некоторой система (для «реального» IO
монады это сама среда выполнения Haskell), а затем, возможно, эта система вызовет обработчик, которому мы предоставили ответ. Это, конечно, также не дает никаких указаний на то, как PutStr "hello world"
должен обрабатываться подобный запрос , но также не претендует на это. Это ясно показывает, что это делегируется какой-то другой системе. Эта модель также довольно точна. Все пользовательские программы в современных ОС должны делать запросы к ОС, чтобы что-либо делать.
Эта модель обеспечивает правильную интуицию. Например, многие начинающие рассматривают такие вещи, как <-
оператор, как «развёртывание» IO
, или имеют (к сожалению, усиленные) представления, которые IO String
, скажем, являются «контейнером», который «содержит» String
s (а затем <-
выводит их). Это представление запрос-ответ делает эту перспективу явно неправильной. Там нет файлового дескриптора внутри OpenFile "foo" (\r -> ...)
. Распространенная аналогия, подчеркивающая это, состоит в том, что в рецепте для пирога нет торта (или, может быть, в этом случае «счет» будет лучше).
Эта модель также легко работает с параллелизмом. Мы можем легко иметь конструктор для OSRequest
like, Fork :: (OSResponse -> IO ()) -> OSRequest
и тогда среда выполнения может чередовать запросы, генерируемые этим дополнительным обработчиком, с обычным обработчиком, как ему нравится. С некоторой сообразительностью вы можете использовать это (или связанные с ним методы) для того, чтобы на самом деле более точно моделировать такие вещи, как параллелизм, вместо того, чтобы просто сказать «мы делаем запрос к ОС, и все происходит». Вот как работает IOSpec
библиотека .
1 Объятия использовали реализацию на основе продолжения из IO
которого примерно подобно тому , что я описать хотя и с непрозрачными функциями вместо явного типа данных. HBC также использовал реализацию, основанную на продолжениях, поверх старого ввода-вывода, основанного на запросе-ответе. NHC (и, таким образом, YHC) использовали thunks, то есть, IO a = () -> a
хотя они и ()
назывались World
, но не передают состояния. JHC и UHC использовали в основном тот же подход, что и GHC.