2015-08-15 118 views
4

我是一位Haskell新手,他在理解seq時遇到了問題,我在下面的示例中對此進行了總結。瞭解Haskell seq

下面是相同(傻)功能的2個實現。該函數採用正整數n並返回元組(n,n)。答案是通過使用具有元組累加器的輔助函數從(0,0)開始計數而產生的。爲了避免構建一個大型的thunk,我使用seq來使得累加器嚴格。

testSeq1元組的內容是嚴格的,而在testSeq2元組本身是嚴格的。我認爲這兩個實現將具有相同的性能。但實際上testSeq1的'使用總內存'僅爲1MB,而testSeq2(當使用n = 1000000進行測試時)爲187MB。

testSeq2有什麼問題?

testSeq1 :: Int -> (Int,Int) 
testSeq1 n = impl n (0,0) where 
    impl 0 (a,b) = (a,b) 
    impl i (a,b) = seq a $ seq b $ impl (i-1) (a+1, b+1) 

testSeq2 :: Int -> (Int,Int) 
testSeq2 n = impl n (0,0) where 
    impl 0 acc = acc 
    impl i acc = seq acc $ impl (i-1) ((fst acc)+1, (snd acc)+1) 

回答

5

seq只強制對其第一個參數的淺評估。你可以看到這兩個例子:

errorTuple :: (Int, Int) 
errorTuple = undefined 

errorTupleContents :: (Int, Int) 
errorTupleContents = (undefined, undefined) 

case1 = errorTuple `seq` (1, 1) 
case2 = errorTupleContents `seq` (1, 1) 

case1將失敗,並undefined錯誤,因爲seq試圖迫使errorTuple的評價,這是undefined,然而,case2不會,因爲元組構造進行評價和回報一個內容未被評估的元組。如果他們被評估,他們會是undefined

+0

對於元組構造函數來說,這是一種特殊的處理方法,還是你說對任何構造函數的參數都不會發生評估?假設我創建了自己的元組數據類型,而不是使用內置的元組,結果會是一樣的嗎? – BillyBadBoy

+0

這不是特殊的元組;發生這種情況是因爲Haskell是懶惰的,只會根據需要進行評估。你可以用'Just undefined'或者你自己的數據類型做類似的例子來測試。 – Tordek

+0

這對我來說很震驚。我認爲seq的全部重點是能夠覆蓋Haskell的默認「懶惰」並迫使它完全評估一個表達式。這解釋了爲什麼我嘗試使用foldl'沒有按照我的預期工作 - 我在我的累加器函數中使用了元組構造函數。 – BillyBadBoy

7

seq荷蘭國際集團的元組只有迫使它要儘可能暴露其元組構造進行評價,但是不會評價及其組件。

也就是說,

let pair = id (1+1, 2+2) 
in seq pair $ ... 

將適用id併產生(_thunk1, _thunk2)其中_thunk的立場給的增加,這是不是在這個時間進行評估。

在你的第二個例子中,你強制累加器acc,但不是它的組件,因此一些大的thunk仍然建立起來。

你可以使用一個所謂的評估策略,如:

evalPair :: (a,b) ->() 
evalPair (a,b) = seq a $ seq b() 

testSeq2 :: Int -> (Int,Int) 
testSeq2 n = impl n (0,0) where 
impl 0 acc = acc 
impl i acc = seq (evalPair acc) $ impl (i-1) ((fst acc)+1, (snd acc)+1) 

但隨後,testSeq1是一個簡單的方法。

作爲另一種替代方案,請使用strict tuples。這些元組永遠不會有組件的thunk,但只能評估結果。正如你所期望的那樣,強制元組構造函數也會強制組件。

import Data.Strict.Tuple 

testSeq2 :: Int -> Pair Int Int 
testSeq2 n = impl n (0 :!: 0) where 
impl 0 acc = acc 
impl i acc = seq acc $ impl (i-1) ((fst acc + 1) :!: (snd acc + 1))