4

我找到了一些示例代码,并对其进行了一些更改

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 。

为什么会发生这种情况,我能做些什么来解决它?

4

3 回答 3

10

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

一个警告:unsafePerformIO在你知道的引用透明的 IO 操作以外的任何东西上使用都是危险的。它可能会与某些优化产生不良交互,并且通常不会像您期望的那样表现。它的名字中有“不安全”这个词是有原因的。

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

于 2010-12-16T14:23:16.727 回答
4

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

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

然后你打算调用的任何地方都newNode应该是一个 IO 操作本身。

另一个小细节:默认情况下counter将具有类型IORef Integer,除非您使用default. 不要试图给它一个泛型类型,比如Num a => IORef a: 那样危险就存在。

于 2010-12-16T22:27:37.253 回答
3

你不应该在正常的编程中使用这样的结构,因为编译器可能会应用各种优化来杀死预期的效果或使其不可预测。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 对您的问题的回答感到难过。您所解释的内容特别有用,如果您有一些全局可用的东西(如配置),不太可能改变,它必须在几乎任何地方都可用。明智地使用它,因为它违反了纯洁的基本原则。如果可能,请改用单子(就像我解释的那样)。

于 2010-12-16T14:44:47.017 回答