对IORefs制造反击的困惑

时间:2010-12-16 14:19:01

标签: haskell monads ioref unsafe-perform-io

我找到了一些示例代码,并稍微改了一下

counter = unsafePerform $ newIORef 0

newNode _ = unsafePerformIO $
              do
                i <- readIORef counter
                writeIORef counter (i+1)
                return i

每次运行时返回1然后是2然后是3然后是3等。

但是当我把它改成

newNode = unsafePerformIO $
              do
                i <- readIORef counter
                writeIORef counter (i+1)
                return i

然后每次运行它都会得到0。

为什么会发生这种情况,我该怎么做才能解决这个问题?

3 个答案:

答案 0 :(得分:10)

在您的第二个版本中,newNode是一个简单的值,而不是一个函数。因此,haskell只对其进行一次评估,然后在您访问newNode时为您提供评估结果。

警告:使用unsafePerformIO以外的任何其他操作都是危险的,你知道这些操作是公开透明的。它可能与某些优化相互影响很严重,而且通常不像您期望的那样。有一个原因是它的名字中有“不安全”这个词。

作为一种使用unsafePerformIO代码的方法很好,但是如果您想在实际代码中使用类似的东西,我强烈建议您重新考虑。

答案 1 :(得分:4)

正如一个澄清:对于像你似乎正在构建的应用程序一样,使用unsafePerformIO创建 IORef是很正常的,并允许Haskell在程序中最接近全局变量语言。在你的行动中使用它可能不明智。例如,newNode最好写成:

newNode = do i <- readIORef counter
             writeIORef counter (i+1)
             return i

然后,您打算调用newNode的任何地方都应该是IO行动。

另一个小细节:默认情况下,counter的类型为IORef Integer,除非您使用default进行更改。不要试图给它一个像Num a => IORef a那样的通用类型:这就是危险所在。

答案 2 :(得分:3)

Yous不应该在正常编程中使用这样的构造,因为编译器可能会应用各种优化来消除所需的效果或使其不可预测。如果可能,请使用类似State monad的内容。它更干净,总是表现得像你想要的那样。你走了:

-- This function lets you escape from the monad, it wraps the computation into a monad to make it, well, countable
counter :: (State Int x) -> x
counter = flip evalState 0

-- Increments the counter and returns the new counter
newNode :: State Int Int
newNode = modify (+1) >>= get

请查看sepp2k对您的问题的答案感到悲伤。你解释的内容特别有用,如果你有全球可用的东西(比如配置),不太可能改变,几乎无处不在。明智地使用它,因为它违背了纯洁的基本原则。如果可能,请使用monads(就像我解释的那样)。