2014-05-23 89 views
3

我很關心哈斯克爾懶惰評價的效率。 考慮下面的代碼我很困惑哈斯克爾的懶惰評價

main = print $ x + x 
    where x = head [1..] 

這裏,x第一持有head [1..]而不是結果1表達,由於懶惰, 但後來當我打電話x + x,將表達head [1..]執行兩次?

我發現haskell.org下面的描述中

懶惰評價,另一方面,意味着只有需要它的結果時,計算表達式(注意,從「還原」轉向「評價」) 。因此,當評估引擎看到一個表達式時,它會構建一個thunk數據結構,其中包含評估表達式所需的任何值以及指向表達式本身的指針。當實際需要結果時,評估引擎調用表達式,然後用結果替換thunk以備將來參考。

那麼,這是否意味着,在x + x,稱第一x時,head [1..]執行和x被重新分配給1,第二x只是調用它的參考?

我理解這個權利嗎?

回答

10

這是關於特定Haskell實現的問題,而不是關於Haskell本身的問題,因爲語言對於如何評估事物沒有特別的保證。

但是在GHC(以及大多數其他實現中,據我所知):是的,當thunk被評估時,它們被內部結果取代,所以其他引用同樣的thunk從評估它的工作中受益第一次。

需要注意的是,對於哪些表達式最終實現爲對同一個thunk的引用沒有真正的保證。一般情況下,只要結果相同,編譯器就可以對您喜歡的代碼進行任何轉換。當然,在編譯器中實現代碼轉換的原因通常是爲了加快代碼的速度,所以希望不會重寫某些東西,使其變得更糟,但它永遠不會是完美的。在實踐中,假設每當你給一個表達式一個名字(如在where x = head [1..]中),那麼你通常很安全,那麼該名字的所有用法(在綁定範圍內)將是對單個thunk的引用。

3

有兩種方法來評估的表達式:

  1. 懶惰(評價最外側的第一)。
  2. 嚴格(評估最裏面的第一個)。

考慮下面的函數:

select x y z = if x > z then x else y 

現在,讓我們把它叫做:

select (2 + 3) (3 + 4) (1 + 2) 

這將如何進行評估?

嚴格評價:評價最裏面的第一個。

select (2 + 3) (3 + 4) (1 + 2) 

select 5 (3 + 4) (1 + 2) 

select 5 7 (1 + 2) 

select 5 7 3 

if 5 > 3 then 5 else 7 

if True then 5 else 7 

5 

嚴格的評估花了6個減少。要評估select我們首先必須評估它的論點。在嚴格評估中,函數的參數總是被充分評估。因此功能是「按價值調用」。因此沒有額外的簿記。

懶惰評價:評價最外面的第一個。

select (2 + 3) (3 + 4) (1 + 2) 

if (2 + 3) > (1 + 2) then (2 + 3) else (3 + 4) 

if 5 > (1 + 2) then 5 else (3 + 4) 

if 5 > 3 then 5 else (3 + 4) 

if True then 5 else (3 + 4) 

5 

懶惰評價只需要5減少。我們從未使用過(3 + 4),因此我們從未評估過它。在懶惰評估中,我們可以評估一個函數而不用評估它的參數。參數只在需要時才被評估。因此功能是「按需呼叫」。

然而,「按需呼叫」評估策略需要額外的簿記 - 您需要跟蹤是否評估了表達式。在上述表達式中,當我們評估x = (2 + 3)時,我們不需要再次評估它。但是,我們確實需要跟蹤它是否得到評估。


Haskell支持嚴格和懶惰的評估。但是它默認支持懶評估。要啓用嚴格的評估,您必須使用特殊功能seqdeepSeq

同樣,您可以在JavaScript等嚴格語言中進行懶惰評估。但是,您需要跟蹤表達式是否已經過評估。你可以研究用JavaScript或類似語言實現thunk。

9

起初,x只是一個thunk。你可以看到如下:

λ Prelude> let x = head [1..] 
λ Prelude> :sprint x 
x = _ 

這裏_表明x尚未評估。它的純粹定義被記錄下來。

然後,您可以瞭解如何構建x + x只是意識到x是一個指向此thunk的指針:這兩個x將指向相同的thunk。一旦被評估,另一個是,因爲它是相同的thunk。

你可以看到,ghc-vis

λ Prelude> :vis 
λ Prelude> :view x 
λ Prelude> :view x + x 

應該表現出你的線沿線的東西:

thunk

在這裏你可以看到x + x thunk的實際指向兩次向x咚。

現在,如果你評估x,通過打印出來,例如:

λ Prelude> print x 

你會獲得:

evaluated

您可以在這裏看到的是,x thunk是不再thunk:值爲1.