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