Способ избежать общего использования 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 в состоянии с высоким уровнем изменчивости. В настоящее время невозможно безопасно абстрагировать этот шаблон без дополнительной поддержки компилятора.