繁体   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