簡體   English   中英

列表推導中的條款

[英]where clauses in list comprehensions

以下兩個公式有什么區別?

cp [] = [[]]
cp (xs:xss) = [x:ys | x <- xs, ys <- cp xss]
----------------------------------------------
cp [] = [[]]
cp (xs:xss) = [x:ys | x <- xs, ys <- yss]
              where yss = cp xss

樣本輸出: cp [[1,2,3],[4,5]] => [[1,4],[1,5],[2,4],[2,5],[3,4],[3,5]]

根據與Haskell的功能性思考 (p.92),第二個版本是“更有效的定義... [保證cp xss只計算一次”,盡管作者從未解釋過為什么。 我原以為他們是等同的。

當然,這兩個定義在它們表示相同值的意義上是等價的。

在操作上,它們在按需調用評估中的共享行為方面存在差異。 jcast已經解釋了為什么,但是我想添加一個不需要明確地去除列表理解的快捷方式。 規則是:每次將變量x綁定到某個值時,語法上處於可依賴於變量x的位置的任何表達式都將被重新計算,即使該表達式實際上不依賴於x

在您的情況下,在第一個定義中, xcp xss出現的位置的范圍內,因此將對xs每個元素x重新計算cp xss 在第二個定義中, cp xss出現在x的范圍之外,因此它只會被計算一次。

然后通常的免責聲明適用,即:

  • 編譯器不需要遵循按需調用評估的操作語義,只需遵循指稱語義。 因此,根據上述規則,它可能會比您預期的更少次數(浮動)或更多次(浮動)。

  • 一般而言,更多共享更好是不正確的。 在這種情況下,例如,它可能不會更好,因為cp xss的大小增長速度與首先計算它的工作量一樣快。 在這種情況下,從內存中讀取值的成本可能超過重新計算值的成本(由於緩存層次結構和GC)。

好吧,一個天真的脫糖將是:

cp [] = [[]]
cp (xs:xss) = concatMap (\x -> concatMap (\ ys -> [ x:ys ]) (cp xss)) xs
----------------------------------------------
cp [] = [[]]
cp (xs:xss) = let yss = cp xss in concatMap (\x -> concatMap (\ ys -> [ x:ys ]) yss) xs

如您所見,在第一個版本中,調用cp xss位於lambda中。 除非優化器移動它,否則每次調用函數\\x -> concatMap (\\ ys -> [ x:ys ]) (cp xss)都會重新評估它。 通過浮動它,我們避免重新計算。

同時,GHC確實有一個優化傳遞來從這樣的循環中浮動昂貴的計算,因此它可以自動將第一個版本轉換為第二個版本。 你的書說第二個版本'保證'只計算一次cp xss的值,因為如果表達式的計算成本很高,編譯器通常會非常猶豫地內聯它(將第二個版本轉換回第一個版本)。

暫無
暫無

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

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