Способ избежать общего использования unsafePerformIO

Я часто нахожу этот шаблон в коде Haskell:

options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar

...

doSomething :: Foo -> Bar
doSomething = unsafePerformIO $ do
  opt <- readMVar options
  doSomething' where ...

В принципе, есть запись параметров или что-то подобное, которое изначально устанавливается в начале программы. Поскольку программист ленив, он не хочет переносить запись options по всей программе. Он определяет MVar, чтобы сохранить его, определяемый уродливым использованием unsafePerformIO. Программист гарантирует, что состояние устанавливается только один раз и до того, как произойдет какая-либо операция. Теперь каждая часть программы должна снова использовать unsafePerformIO, просто чтобы извлечь параметры.

По-моему, такая переменная считается прагматически чистой (не бить меня). Существует ли библиотека, которая абстрагирует эту концепцию и гарантирует, что переменная задана только один раз, то есть, чтобы до инициализации не выполнялся вызов, и что не нужно писать unsafeFireZeMissilesAndMakeYourCodeUglyAnd DisgustingBecauseOfThisLongFunctionName

Ответы

Ответ 1

Используйте неявные параметры. Они немного менее тяжелы, чем каждая функция имеет Reader или ReaderT в своем типе. Вам нужно изменить сигнатуры типов своих функций, но я думаю, что такое изменение может быть написано сценарием. (Сделал бы приятную функцию для Haskell IDE.)

Ответ 2

Те, кто будет торговать необходимой ссылочной прозрачностью для небольшого временные удобства не заслуживают ни чистоты и удобства.

Это плохая идея. Код, который вы находите, это плохой код. *

Нет возможности полностью обернуть этот шаблон безопасно, потому что это не безопасный шаблон. Не делайте этого в своем коде. Не ищите безопасный способ сделать это. Существует не безопасный способ сделать это. Поместите unsafePerformIO вниз на пол, медленно и отходите от консоли...

* Существуют законные причины, по которым люди используют MVAS верхнего уровня, но эти причины связаны с привязками к внешнему коду по большей части или несколькими другими вещами, где альтернатива очень грязная. Однако в этих случаях, насколько мне известно, MVars верхнего уровня недоступны из-за unsafePerformIO.

Ответ 3

Если вы используете MVar для хранения настроек или чего-то подобного, почему бы вам не попробовать монаду-читателю?

foo :: ReaderT OptionRecord IO ()
foo = do
    options <- ask
    fireMissiles

main = runReaderT foo (OptionRecord "foo")

(И обычный Reader, если вы не требуете ввода-вывода: P)

Ответ 4

Существует важная причина не использовать этот шаблон. Насколько я знаю, в

options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar

Haskell не дает никаких гарантий, что options будет оцениваться только один раз. Поскольку результат option является чистым значением, он может быть замечен и повторно использован, но он также может быть пересчитан для каждого вызова (т.е. Inlined), а значение программы не должно меняться (в отличие от вашего случая).

Если вы все еще решите использовать этот шаблон, обязательно добавьте {-# NOINLINE options #-}, в противном случае он может стать встроенным и ваша программа завершится неудачно! (И этим мы выходим за гарантии, предоставляемые языком и системой типов, и полагаемся исключительно на реализацию конкретного компилятора.)

Эта тема была широко обсуждена, и возможные решения хорошо представлены в Haskell Wiki в состоянии с высоким уровнем изменчивости. В настоящее время невозможно безопасно абстрагировать этот шаблон без дополнительной поддержки компилятора.