2010-12-16 72 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