19

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

options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar

...

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

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

在我看来,这样的变量被认为是务实的纯粹(不要打败我)。是否有一个库可以抽象出这个概念并确保变量只设置一次,即在初始化之前不进行任何调用并且不必编写unsafeFireZeMissilesAndMakeYourCodeUglyAnd DisgustingBecauseOfThisLongFunctionName

4

5 回答 5

21

那些为了一点暂时的便利而牺牲必要的参考透明度的人既不配得纯洁也不配得方便。

这是一个坏主意。您在其中找到的代码是错误的代码。*

没有办法安全地完全包装这个模式,因为它不是一个安全的模式。不要在你的代码中这样做。不要寻找安全的方法来执行此操作。没有一种安全的方法可以做到这一点。将unsafePerformIO羽绒服放在地板上,慢慢地,然后远离控制台……

*人们确实使用顶级 MVar 是有正当理由的,但这些原因在很大程度上与绑定到外部代码有关,或者与其他一些替代方案非常混乱的事情有关。然而,据我所知,在这些情况下,顶级 MVar不能从后面访问unsafePerformIO

于 2011-05-20T18:38:23.747 回答
10

如果您使用 MVar 来保存设置或类似的东西,为什么不试试 reader monad?

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

main = runReaderT foo (OptionRecord "foo")

(如果你不需要 IO 的话,还有普通的阅读器:P)

于 2011-05-20T18:37:17.643 回答
5

使用隐式参数。它们比使每个函数都具有Reader或具有ReaderT其类型要轻一些。您确实必须更改函数的类型签名,但我认为可以编写这样的更改。(对于 Haskell IDE 来说是一个不错的功能。)

于 2011-05-20T19:43:51.277 回答
2

不使用这种模式有一个重要原因。据我所知,在

options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar

Haskell 不保证options只会被评估一次。由于结果option是纯值,因此可以记忆和重用,但也可以为每次调用重新计算(即内联),并且程序的含义不得改变(与您的情况相反)。

如果您仍然决定使用此模式,请务必添加 {-# NOINLINE options #-},否则它可能会被内联并且您的程序将失败!(通过这种方式,我们摆脱了语言和类型系统提供的保证,完全依赖于特定编译器的实现。)

该主题已被广泛讨论,并且可能的解决方案在顶级可变状态的 Haskell Wiki 上得到了很好的总结。目前,如果没有一些额外的编译器支持,就不可能安全地抽象这种模式。

于 2014-05-24T06:49:34.557 回答
2

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

阅读不同的代码。

由于程序员比较懒惰,他不想在整个程序中携带选项记录。他定义了一个 MVar 来保存它 - 由 unsafePerformIO 的丑陋使用定义。程序员确保状态只设置一次,并且在任何操作发生之前。现在程序的每个部分都必须再次使用 unsafePerformIO,只是为了提取选项。

听起来就像 reader monad 所做的完全一样,除了 reader monad 以安全的方式完成它。与其适应自己的懒惰,不如编写真正的好代码。

于 2018-04-13T23:48:23.867 回答