2010-12-16 56 views
4

我发现了一些示例代码,并改变它一点混乱了IORefs做出反

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每次运行它的时候。

为什么会发生这种情况,我该如何解决这个问题?

回答

10

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

警告一句话:在除了IO动作之外的其他任何事情上使用unsafePerformIO,您知道这种动作是透明透明的,这是危险的。它可能会与一些优化进行很糟糕的交互,并且通常不会像您期望的那样行事。有一个原因是它的名字中包含“不安全”这个词。

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

+0

为什么它不被视为一个非函数函数? – Squidly 2010-12-16 15:10:41

+6

@MrBones:因为这种行为几乎不会是人们想要的。如果我写'x = veryExpensiveFunction foobar',然后'y = x * x + x',我希望'veryExpensiveFunction foobar'被评估一次,而不是三次。这正是Haskell所做的(除非'x''的类型是多态的)。在这种情况下,您希望它表现为无功能的唯一原因是评估表达式具有副作用,如果没有不安全的操作,这种情况甚至不会发生。 – sepp2k 2010-12-16 15:19:46

+1

haskell中没有这样的事物作为一个非函数函数。函数都取一个值(可能是一个元组)并返回一个值(可能是另一个函数)。函数不被“调用” - 它们被应用于值,以产生值。你是否真的希望'foo = 1 + 2'成为告诉处理器在你每次使用foo!时加1和2的函数? – sclv 2010-12-16 15:22:01

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悲伤为您的问题的答案。如果你有全局可用的东西(比如配置文件),那么你解释的东西特别有用,不太可能改变,而这几乎无处不在。明智地使用它,因为它违背了纯洁的基本原则。如果可能的话,请使用monad(如我所解释的)。

+8

在这里强调答案 - 解决这个问题的最好方法是永远不会永远*永远**使用'unsafePerformIO',除非你*真的*真的*真的* **真正**只是知道什么你在做,为什么你需要它。即使如此,也可能不使用它。 – sclv 2010-12-16 15:23:48

4

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

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

然后任何地方,你打算叫newNode应该是一个IO动作本身。

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

+1

出于好奇,使用'Num a => IORef a'说的危险是什么? – 2010-12-17 02:03:56

+0

因为类型系统被它搞砸了。我在某处读到这个消息,但是再也找不到资源。可能在'unsafePerformIO'的源代码中可能出现这种情况@ – fuz 2010-12-17 04:21:51

+1

@Grazer:它可能违反类型安全;请参阅[unsafePerformIO'的文档](http://hackage.haskell.org/packages/archive/base/latest/doc/html/System-IO-Unsafe.html#v:unsafePerformIO)。 (尽管他们的例子现在为我打印'“*”',而不是倾销核心,但仍然不应该这样做。)您也可以生成一个值,在您第二次评估它时会发生变化。 – 2010-12-17 04:29:47