我找到了一些示例代码,并稍微改了一下
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。
为什么会发生这种情况,我该怎么做才能解决这个问题?
答案 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(就像我解释的那样)。