簡體   English   中英

在無限列表上左右折疊

[英]Left and Right Folding over an Infinite list

我對“ 了解您的Haskell”一文的以下段落有疑問(很棒的imo書,不是廢話):

一個很大的不同是,右折疊在無限列表上起作用,而左折疊則不行! 簡而言之,如果您在某個時間點取一個無限的列表,然后將其從右側折疊起來,最終您將到達列表的開頭。 但是,如果您在某個點取一個無限的列表,然后嘗試將其從左向上折疊,那么您將永遠無法結束!

我就是不明白這一點。 如果您要獲取一個無限列表,然后嘗試從右側將其折疊起來,那么您就必須從無窮大的那一點開始,這幾乎是沒有發生的(如果有人知道您可以使用的語言,請告訴:p )。 至少,您必須根據Haskell的實現從那里開始,因為在Haskell foldr和foldl中,不需要使用確定在列表中應該從何處開始折疊的參數。

我同意引號iff foldr和foldl的參數確定了它們應該在列表中的何處開始折疊,因為這是有意義的,如果您采用無限列表並從已定義的索引開始就開始折疊,則它最終終止,而不會不管您從左折處開始, 您將向無限折疊。 但是foldr和foldl 接受此參數,因此引號沒有意義。 在Haskell中,無限列表上的左折和右折都不會終止

我的理解正確嗎?或者我缺少什么?

這里的關鍵是懶惰。 如果用於折疊列表的函數很嚴格,則給定無限列表,向左折疊或向右折疊都不會終止。

Prelude> foldr (+) 0 [1..]
^CInterrupted.

但是,如果嘗試折疊不太嚴格的功能,則會得到終止結果。

Prelude> foldr (\x y -> x) 0 [1..]
1

您甚至可以得到一個無限數據結構的結果,因此盡管它在某種意義上不會終止,但仍然可以產生可以延遲使用的結果。

Prelude> take 10 $ foldr (:) [] [1..]
[1,2,3,4,5,6,7,8,9,10]

但是,這不適用於foldl ,因為您將永遠無法評估最外部的函數調用,無論是否延遲。

Prelude> foldl (flip (:)) [] [1..]
^CInterrupted.
Prelude> foldl (\x y -> y) 0 [1..]
^CInterrupted.

請注意,左右折疊之間的關鍵區別不是列表的遍歷順序(始終從左到右),而是嵌套的結果函數應用程序的順序。

  • 使用文件foldr ,它們嵌套在“內部”

     foldr fy (x:xs) = fx (foldr fy xs) 

    在這里,第一次迭代將導致f的最外層應用。 因此, f有機會變得懶惰,以便不總是對第二個參數進行求值,或者它可以產生數據結構的某些部分而不會強迫其第二個參數。

  • 使用foldl ,它們嵌套在“外部”

     foldl fy (x:xs) = foldl f (fyx) xs 

    在這里,我們無法評估任何東西,直到我們到達f的最外層應用為止,無論f是否嚴格,在無限列表的情況下我們都無法達到。

關鍵詞是“在某個時候”。

如果您在某個時間點取了一個無限列表然后將其從右側折疊起來,最終您將到達列表的開頭。

因此,您是對的,您不可能從無限列表的“最后一個”元素開始。 但是作者的觀點是:假設可以。 只需在遠處選擇一個點(對於工程師來說,這“足夠接近”到無窮大)並開始向左折疊。 最終,您最終會在列表的開頭。 對於左折,情況並非如此,如果您在那兒選擇一個點waaaay(並將其稱為“足夠接近”到列表的開頭),然后開始向右折,您還有無限的路要走。

因此,訣竅在於,有時您無需達到無窮大。 您可能甚至不需要去那里。 但是您可能不知道需要走多遠,在這種情況下,無限列表非常方便。

簡單的示例是foldr (:) [] [1..] 讓我們進行折疊。

回想一下,文件foldr fz (x:xs) = fx (foldr fz xs) 在無限列表上, z實際上並不重要,因此我只是將其保留為z而不是[] ,這會使插圖混亂

foldr (:) z (1:[2..])         ==> (:) 1 (foldr (:) z [2..])
1 : foldr (:) z (2:[3..])     ==> 1 : (:) 2 (foldr (:) z [3..])
1 : 2 : foldr (:) z (3:[4..]) ==> 1 : 2 : (:) 3 (foldr (:) z [4..])
1 : 2 : 3 : ( lazily evaluated thunk - foldr (:) z [4..] )

看看文件foldr如何折疊,盡管從理論上說是從右側折疊的,但實際上是從左側開始將結果列表中的各個元素都拉出來了嗎? 因此,如果您從此列表中take 3 ,則可以清楚地看到它將能夠產生[1,2,3]並且無需進一步評估折數。

請記住,在Haskell中,由於延遲計算,您可以使用無限列表。 因此, head [1..]僅為1,而head $ map (+1) [1..]為2,即使`[1 ..]無限長。 如果沒有得到,請停下來玩一會兒。 如果您知道,請繼續閱讀...

我認為,造成混淆的部分原因是, foldlfoldr總是從一側或另一側開始,因此您無需給出長度。

foldr定義很簡單

 foldr _ z [] = z
 foldr f z (x:xs) = f x $ foldr f z xs

為什么這會在無限列表上終止,請嘗試

 dumbFunc :: a -> b -> String
 dumbFunc _ _ = "always returns the same string"
 testFold = foldr dumbFunc 0 [1..]

在這里,我們將一個“”(因為值無關緊要)和無窮自然數列表傳遞到文件foldr 這會終止嗎? 是。

它終止的原因是因為Haskell的評估等同於惰性術語重寫。

所以

 testFold = foldr dumbFunc "" [1..]

變為(允許模式匹配)

 testFold = foldr dumbFunc "" (1:[2..])

與(根據我們對fold的定義)相同

 testFold = dumbFunc 1 $ foldr dumbFunc "" [2..]

現在通過dumbFunc的定義,我們可以得出結論

 testFold = "always returns the same string"

當我們具有執行某些功能但有時很懶的功能時,這會更有趣。 例如

foldr (||) False 

用於查找列表是否包含任何True元素。 我們可以使用它來定義高階函數,只要且僅當傳入的函數對於列表的某些元素為true時, any函數均返回True

any :: (a -> Bool) -> [a] -> Bool
any f = (foldr (||) False) . (map f)

關於惰性評估的好處是,當遇到第一個元素e時,它將停止,例如fe == True

另一方面, foldl並非如此。 為什么? 好吧,一個非常簡單的foldl看起來像

foldl f z []     = z                  
foldl f z (x:xs) = foldl f (f z x) xs

現在,如果我們嘗試上面的示例會發生什么

testFold' = foldl dumbFunc "" [1..]
testFold' = foldl dumbFunc "" (1:[2..])

現在變成:

testFold' = foldl dumbFunc (dumbFunc "" 1) [2..]

所以

testFold' = foldl dumbFunc (dumbFunc (dumbFunc "" 1) 2) [3..]
testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) [4..]
testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) 4) [5..]

等等等等。 我們永遠無法到達任何地方,因為Haskell總是首先評估最外部的函數(簡而言之就是惰性評估)。

一個很酷的結果是,您可以在foldr之外實現foldl ,反之亦然。 這意味着在某種程度上, foldr是所有高階字符串函數中最基礎的,因為它是我們用於實現幾乎所有其他函數的函數。 有時您仍可能需要使用foldl ,因為您可以遞歸實現foldl tail,並從中獲得一些性能提升。

Haskell Wiki上有很好的解釋。 它顯示了使用不同類型的折疊和累加器功能的逐步還原。

您的理解是正確的。 我想知道作者是否正在嘗試談論Haskell的惰性評估系統(在該系統中,您可以將無限列表傳遞給不包括fold的各種函數,並且僅評估需要多少才能返回答案)。 但我同意您的觀點,即作者在描述該段中的內容時做得不好,並且說錯了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM