簡體   English   中英

如何在Haskell(GHC)中實現列表?

[英]How are lists implemented in Haskell (GHC)?

我只是好奇Haskell中列表的一些確切的實現細節(GHC特定的答案很好) - 他們是天真的鏈接列表,還是他們有任何特殊的優化? 進一步來說:

  1. length(!!) (例如)是否必須遍歷列表?
  2. 如果是這樣,他們的值是否以任何方式緩存(即,如果我調用兩次length ,它是否必須迭代兩次)?
  3. 訪問列表后面是否涉及遍歷整個列表?
  4. 是否記住了無限列表和列表推導? (即,對於fib = 1:1:zipWith (+) fib (tail fib) ,每個值是遞歸計算的,還是依賴於先前的計算值?)

任何其他有趣的實施細節將不勝感激。 提前致謝!

列表在Haskell中沒有特殊的操作處理。 它們的定義如下:

data List a = Nil | Cons a (List a)

只是使用一些特殊的符號: [a]表示List a[]表示Nil(:)表示Cons 如果您定義了相同並重新定義了所有操作,您將獲得完全相同的性能。

因此,Haskell列表是單鏈接的。 由於懶惰,它們通常用作迭代器。 sum [1..n]在常量空間中運行,因為此列表的未使用前綴隨着總和的進展而被垃圾收集,並且在需要之前不會生成尾部。

至於#4:Haskell中的所有值都被記憶,除了函數沒有為它們的參數保留一個memo表。 所以當你像你一樣定義fib時,結果將被緩存,並且第n個斐波納契數將在O(n)時間內被訪問。 但是,如果您以這種明顯相同的方式定義它:

-- Simulate infinite lists as functions from Integer
type List a = Int -> a

cons :: a -> List a -> List a
cons x xs n | n == 0    = x
            | otherwise = xs (n-1)

tailF :: List a -> List a
tailF xs n = xs (n+1)

fib :: List Integer
fib = 1 `cons` (1 `cons` (\n -> fib n + tailF fib n))

(花一點時間注意你的定義的相似性)

然后不共享結果,並且將在O(fib n)(指數)時間訪問第n個斐波納契數。 您可以說服函數與memoization庫共享,例如data-memocombinators

據我所知(我不知道這有多少是針對GHC的)

  1. length(!!) DO必須遍歷列表。

  2. 我不認為列表有任何特殊的優化,但有一種技術適用於所有數據類型。

    如果你有類似的東西

     foo xs = bar (length xs) ++ baz (length xs) 

    那么length xs將被計算兩次。

    但如果相反,你有

     foo xs = bar len ++ baz len where len = length xs 

    那么它只會被計算一次。

  3. 是。

  4. 是的,一旦計算了命名值的一部分,它將被保留,直到名稱超出范圍。 (該語言不需要這個,但這是我理解實現行為的方式。)

如果是這樣,他們的值是否以任何方式緩存(即,如果我調用兩次長度,它是否必須迭代兩次)?

GHC不執行完全的共同表達消除 例如:

{-# NOINLINE aaaaaaaaa #-}
aaaaaaaaa :: [a] -> Int
aaaaaaaaa x = length x + length x

{-# NOINLINE bbbbbbbbb #-}
bbbbbbbbb :: [a] -> Int
bbbbbbbbb x = l + l where l = length x

main = bbbbbbbbb [1..2000000] `seq` aaaaaaaaa [1..2000000] `seq` return ()

給出-ddump-simpl

Main.aaaaaaaaa [NEVER Nothing] :: forall a_adp.
                                  [a_adp] -> GHC.Types.Int
GblId
[Arity 1
 NoCafRefs
 Str: DmdType Sm]
Main.aaaaaaaaa =
  \ (@ a_ahc) (x_adq :: [a_ahc]) ->
    case GHC.List.$wlen @ a_ahc x_adq 0 of ww_anf { __DEFAULT ->
    case GHC.List.$wlen @ a_ahc x_adq 0 of ww1_Xnw { __DEFAULT ->
    GHC.Types.I# (GHC.Prim.+# ww_anf ww1_Xnw)
    }
    }

Main.bbbbbbbbb [NEVER Nothing] :: forall a_ado.
                                  [a_ado] -> GHC.Types.Int
GblId
[Arity 1
 NoCafRefs
 Str: DmdType Sm]
Main.bbbbbbbbb =
  \ (@ a_adE) (x_adr :: [a_adE]) ->
    case GHC.List.$wlen @ a_adE x_adr 0 of ww_anf { __DEFAULT ->
    GHC.Types.I# (GHC.Prim.+# ww_anf ww_anf)
    }

請注意, aaaaaaaaa兩次調用GHC.List.$wlen

(事實上​​,因為x需要保留在aaaaaaaaa ,所以它比bbbbbbbbb慢2 bbbbbbbbb 。)

暫無
暫無

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

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