Это предполагаемая «интерпретация» 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, скажем, являются «контейнером», который «содержит» Strings (а затем <-выводит их). Это представление запрос-ответ делает эту перспективу явно неправильной. Там нет файлового дескриптора внутри OpenFile "foo" (\r -> ...). Распространенная аналогия, подчеркивающая это, состоит в том, что в рецепте для пирога нет торта (или, может быть, в этом случае «счет» будет лучше).
Эта модель также легко работает с параллелизмом. Мы можем легко иметь конструктор для OSRequestlike, Fork :: (OSResponse -> IO ()) -> OSRequestи тогда среда выполнения может чередовать запросы, генерируемые этим дополнительным обработчиком, с обычным обработчиком, как ему нравится. С некоторой сообразительностью вы можете использовать это (или связанные с ним методы) для того, чтобы на самом деле более точно моделировать такие вещи, как параллелизм, вместо того, чтобы просто сказать «мы делаем запрос к ОС, и все происходит». Вот как работает IOSpecбиблиотека .
1 Объятия использовали реализацию на основе продолжения из IOкоторого примерно подобно тому , что я описать хотя и с непрозрачными функциями вместо явного типа данных. HBC также использовал реализацию, основанную на продолжениях, поверх старого ввода-вывода, основанного на запросе-ответе. NHC (и, таким образом, YHC) использовали thunks, то есть, IO a = () -> aхотя они и ()назывались World, но не передают состояния. JHC и UHC использовали в основном тот же подход, что и GHC.