Я не уверен, поможет ли это (заранее извиняюсь, если это только сбивает с толку), но способ, которым IO делается ссылочно-прозрачным в Mercury, заключается в явной передаче значения типа io
всем выполняющим IO код, который также должен возвращать новое значение типа io
.
Ввод io
представляет «состояние мира» непосредственно перед вызовом кода. весь мир вне программы; содержимое диска, то, что напечатано на экране, что набирает пользователь, что будет получено из сети, все.
Выходные данные io
представляют состояние мира сразу после вызова кода. Разница между входом io
и выходом io
содержит изменения в мире, которые были сделаны этим кодом (плюс все остальное, что произошло снаружи, в теории).
Система режимов Меркурия гарантирует, что значения типа io
являются уникальными ; есть только один из них, поэтому вы не можете передать одно и то же значение io
двум различным процедурам, выполняющим ввод-вывод. Вы передаете io
в процедуру, делая ее бесполезной для вас, а затем получаете новую обратно.
Конечно, реальное состояние реального мира не кодируется в значения типа io
; на самом деле под капотом io
совершенно пусто! Там нет информации, передаваемой на всех! Но io
значения представляют состояние мира.
Вы можете думать о функциях в монаде IO как о том же. Они принимают дополнительный неявный аргумент состояния мира и возвращают дополнительное неявное значение состояния мира. Реализация монады IO обрабатывает передачу этого дополнительного вывода следующей функции. Это делает монаду IO очень похожей на монаду государства; Легко видеть, что get
в этой монаде чисто, хотя, похоже, она не принимает аргументов.
В этом понимании main получает начальное состояние мира до запуска вашей программы и преобразует его в состояние мира после запуска программы, пропуская его через весь код ввода-вывода в вашей программе.
И поскольку вы сами не можете получить оценку состояния мира, у вас нет возможности запустить собственную маленькую цепочку ввода-вывода в середине другого кода. Это то, что обеспечивает чистоту, поскольку на самом деле у нас не может быть совершенно нового мира с его собственным состоянием, возникающим из ниоткуда.
Так что getLine :: IO String
можно рассматривать как нечто вроде getLine :: World -> (World, String)
. Это чисто, потому что все эти разные времена он вызывается и возвращает разные строки, которые он получает разные World
каждый раз.
Если вы думаете о значениях, которые являются действиями ввода-вывода, или о состоянии мира, передаваемом между функциями, или о любом другом механизме, все эти конструкции являются репрезентативными . Под капотом все операции ввода-вывода реализованы с нечистым кодом, потому что так устроен мир; когда вы записываете в файл, вы изменили состояние диска. Но мы можем представить это на более высоком уровне абстракции, что позволяет вам думать об этом по-другому.
Аналогия в том, что вы можете реализовать карту с деревьями поиска или хеш-таблицами или другими способами. Но, реализовав его, когда вы рассуждаете о коде, который использует карту, вы не думаете о левом и правом поддеревьях или сегментах и хэшах, вы думаете об абстракции, которая является картой.
Если мы можем представлять IO таким образом, чтобы поддерживать чистоту и ссылочную прозрачность, то мы можем применить любые рассуждения, которые требуют ссылочной прозрачности, к коду, использующему это представление. Это позволяет работать всей математике, применимой к такому коду (большая часть которой используется при реализации расширенных компиляторов для языков с принудительной чистотой), даже для программ, выполняющих ввод-вывод.
И краткое дополнение к вашему второму вопросу.GHC теоретически может свести эту входную программу к выходному.Я не верю, что это очень трудно сделать, потому что это вообще неразрешимо.Представьте себе программу, которая не требует ввода, но генерирует бесконечный список и затем печатает последние 3 элемента.Теоретически любая программа, которая не зависит от ее входных данных, может быть сокращена до ее выходных данных, но для этого компилятор должен сделать что-то эквивалентное выполнению программы во время компиляции.Таким образом, чтобы сделать это полностью , вы должны быть рады, что ваши программы иногда переходят в бесконечные циклы во время компиляции .И почти каждая программа зависит от ее входных данных, поэтому даже попытка сделать это не так уж велика.
Там - это что-то, что можно получитьвыявление частей программ, не зависящих от какого-либо ввода, и замена их результатом.Это называется частичная оценка , и это активная тема исследования, но это также очень сложно, и нет единого решения для всех.Чтобы сделать это, вы должны быть в состоянии определить области программы, которые не будут отправлять компилятор в бесконечный цикл, пытаясь выяснить, что они возвращают, и вы должны принять решение о том, удалить ли какой-либо код, который занимает несколькоСекунды во время выполнения - это достаточно хорошее преимущество, если оно означает встраивание структуры данных размером в несколько сотен мегабайт, которую она возвращает в двоичный файл программы.И вы должны выполнить весь этот анализ, не тратя часов на компиляцию умеренно сложных программ.