我经常在Haskell代码中找到这种模式:
options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar
...
doSomething :: Foo -> Bar
doSomething = unsafePerformIO $ do
opt <- readMVar options
doSomething' where ...
基本上,人们有一个选项或类似的记录,最初是在程序开始时设置的。由于程序员很懒惰,他不想在整个程序中携带options
记录。他定义了一个MVar
来保持它 - 由unsafePerformIO
的丑陋使用来定义。程序员确保状态只设置一次,并且在任何操作发生之前。现在,程序的每个部分都必须再次使用unsafePerformIO
,只是为了提取选项。
在我看来,这样的变量被认为是语用纯粹的(不要打败我)。是否有一个库抽象出这个概念,并确保变量只设置一次,即在初始化之前没有调用,而且不需要写unsafeFireZeMissilesAndMakeYourCodeUglyAnd
DisgustingBecauseOfThisLongFunctionName
答案 0 :(得分:20)
那些愿意稍微交换必要的参考透明度的人 暂时的便利也不值得 纯洁也不方便。
这是一个坏主意。您在此处找到的代码是错误的代码。*
没有办法安全地完全包裹这个模式,因为它不是一个安全的模式。不要在代码中执行此操作。不要寻找一种安全的方法来做到这一点。没有一种安全的方法可以做到这一点。将unsafePerformIO
放在地板上,慢慢地,然后远离控制台......
*人们确实使用顶级MV的合理原因,但这些原因大部分与外国代码的绑定有关,或者其他一些非常混乱的事情。在这些情况下,据我所知,顶级MVar 不从unsafePerformIO
后面访问。
答案 1 :(得分:9)
如果您使用MVar进行设置或类似设置,为什么不尝试使用阅读器monad?
foo :: ReaderT OptionRecord IO ()
foo = do
options <- ask
fireMissiles
main = runReaderT foo (OptionRecord "foo")
(如果你不需要IO,请定期阅读:P)
答案 2 :(得分:5)
使用隐式参数。它们的重量级略低于使每个函数在其类型中具有Reader
或ReaderT
的重量级。您必须更改函数的类型签名,但我认为这样的更改可以编写脚本。 (这将为Haskell IDE提供一个很好的功能。)
答案 3 :(得分:2)
不使用此模式有一个重要原因。据我所知,在
options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar
Haskell不保证options
只会被评估一次。由于option
的结果是纯值,因此可以对其进行记忆和重复使用,但也可以针对每次调用重新计算(即内联),并且程序的含义不得更改(与您的情况相反)。
如果您仍然决定使用此模式,请务必添加 {-# NOINLINE options #-}
,否则可能会内联并且您的程序将失败! (通过这种方式,我们将摆脱语言和类型系统提供的保证,并完全依赖于特定编译器的实现。)
本主题已被广泛讨论,可能的解决方案在Top level mutable state中的Haskell Wiki上得到了很好的总结。目前,如果没有一些额外的编译器支持,就不可能安全地抽象这种模式。
答案 4 :(得分:1)
我经常在Haskell代码中找到这种模式:
阅读不同的代码。
由于程序员很懒,他不想在整个程序中携带选项记录。他定义了一个MVar来保持它 - 由丑陋的unsafePerformIO定义。程序员确保状态只设置一次,并且在任何操作发生之前。现在程序的每个部分都必须再次使用unsafePerformIO,只是为了提取选项。
听起来就像读者monad完成的那样,除了读者monad以安全的方式做到这一点。而不是适应自己的懒惰,只需写出实际的好代码。