2012-09-05 53 views
11

我想知道爲什麼預期左邊的功能有類型簽名a -> b -> a而不是b -> a -> a。這背後有設計決定嗎?爲什麼fold會預期(a - > b - > a)而不是(b - > a - > a)?

在Haskell中,例如,我必須編寫foldl (\xs x -> x:xs) [] xs來反轉列表而不是更短的foldl (:) [] xs(這可能與b -> a -> a一起使用)。另一方面,有些用例需要標準a -> b -> a。在Scala中,這可以附加:xs.foldLeft(List.empty[Int]) ((xs, x) => xs:+x)可以寫成xs.foldLeft(List.empty[Int]) (_:+_)

做比例更多的用例需要給定的類型簽名而不是替代的簽名,或者是否有其他決定導致可以摺疊的設計在Haskell和Scala(以及可能還有很多其他語言)中?

+4

換句話說,您必須顛倒'(:)'的參數順序才能顛倒列表。我感覺合理。 –

+1

請注意,'mapAccumL'和'mapAccumR'(來自'Data.List' /'Data.Traversable')具有相同的簽名,儘管它們也工作在相反的方向。 – leftaroundabout

回答

28

從概念上講,右折,說foldr f z [1..4]替代以下形式

: 
/\ 
1 : 
/\ 
    2 : 
    /\ 
    3 : 
    /\ 
     4 [] 

的列表,格式如下

f 
/\ 
1 f 
/\ 
    2 f 
    /\ 
    3 f 
    /\ 
     4 z 

的表達式的值。如果我們要代表這表達式在一行中,所有括號將與右側相關,因此名稱正確摺疊:(1 `f` (2 `f` (3 `f` (4 `f` z))))。左折在某種意義上與右折是雙重的。特別是,我們想用於相應圖的形狀爲一個左摺疊成爲其中的鏡像用於左倍,如下面的:

 f 
    /\ 
     f 4 
    /\ 
    f 3 
/\ 
    f 2 
/\ 
z 1 

如果我們上寫出該圖單行線,我們會得到一個表達式,所有的括號聯想到左側,與左倍的名義嘲弄得好:

((((z `f` 1) `f` 2) `f` 3) `f` 4) 

但是請注意,在這面鏡子圖,摺疊的遞歸結果作爲第一個參數提供給f,而列表中的每個元素都作爲第二個參數提供,即參數提供給f與右褶皺相反。

4

因爲您以簡寫形式編寫(a /: bs)foldLeft;這是一個運算符,它通過所有bs推動a,所以用同樣的方式編寫函數是很自然的(即(A,B) => A)。請注意,foldRight以其他順序進行。

+0

不錯,但這隻會解釋爲斯卡拉不在Haskell中,它沒有'/:'。 – sschaef

7

型號簽名是foldl :: (a -> b -> a) -> a -> [b] -> a;組合函數在左邊具有初始值是很自然的,因爲這是它與列表元素結合的方式。同樣,你會注意到foldr有相反的方向。定義反向的複雜性是因爲您使用的是lambda表達式,其中flip本來會更好:foldl (flip (:)) [] xs,它在翻轉和反轉的概念之間也具有令人愉快的相似性。

2

假設你有這樣的:

List(4, 2, 1).foldLeft(8)(_/_) 

這是一樣的:

((8/4)/2)/1 

見的第一個參數是怎麼總是忒蓄電池?按照此順序具有參數可使佔位符語法(下劃線)直接轉換爲擴展表達式。

相關問題