一种避免常见使用unsafePerformIO的方法

时间:2011-05-20 18:23:53

标签: haskell global-variables purely-functional unsafe-perform-io

我经常在Haskell代码中找到这种模式:

options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar

...

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

基本上,人们有一个选项或类似的记录,最初是在程序开始时设置的。由于程序员很懒惰,他不想在整个程序中携带options记录。他定义了一个MVar来保持它 - 由unsafePerformIO的丑陋使用来定义。程序员确保状态只设置一次,并且在任何操作发生之前。现在,程序的每个部分都必须再次使用unsafePerformIO,只是为了提取选项。

在我看来,这样的变量被认为是语用纯粹的(不要打败我)。是否有一个库抽象出这个概念,并确保变量只设置一次,即在初始化之前没有调用,而且不需要写unsafeFireZeMissilesAndMakeYourCodeUglyAnd DisgustingBecauseOfThisLongFunctionName

5 个答案:

答案 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)

使用隐式参数。它们的重量级略低于使每个函数在其类型中具有ReaderReaderT的重量级。您必须更改函数的类型签名,但我认为这样的更改可以编写脚本。 (这将为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以安全的方式做到这一点。而不是适应自己的懒惰,只需写出实际的好代码。