为什么Haskell列表理解比循环更快

时间:2015-02-11 10:40:50

标签: haskell

我是Haskell noob。在尝试循环和列表推导时,我发现列表理解比循环更快。 Lisp理解使用取幂并且仍然更快。为什么呢?

lg :: Int -> Int -> Int -> Int
lg s e a =
  if s < e
  then
  if even s
  then
    lg (s+1) e (a+1)
  else
    lg (s+1) e (a*2)
  else
    a

loopGrowth :: Int -> Int
loopGrowth x = lg 0 x 0

calcGrowth :: Int -> Int
calcGrowth y =
  last (take (y+1) (tail (join [ [2 ^ x - 2 ,2 ^ x - 1] | x <- [1..50] ])))

2 个答案:

答案 0 :(得分:5)

首先是一些风格建议:

lg s e a
  | s >= e     = a
  | even s     = lg (s+1) e (a+1)
  | otherwise  = lg (s+1) e (a*2)

关于你的问题:lg实际上不是一个循环。它是一个尾递归函数,但仅此一点在Haskell中并没有多少说明。阻碍的主要因素是懒惰:如果累加器只是在thunk维度而不是它们的值中累积,那么懒惰只会产生大量的开销而没有任何可用性。

防止这种情况的一种简单方法是使用

严格评估参数
{-# LANGUAGE BangPatterns     #-}

lg' s e !a
  | s >= e     = a
  | even s     = lg' (s+1) e (a+1)
  | otherwise  = lg' (s+1) e (a*2)

然而,由于这些问题,但也因为简洁/模块化/ ...,最好不要编写尾递归函数,而是使用更高级别的工具 - 列表推导本身通常性能很好,但如果你用通用折叠等方式表达你的算法,你可以利用更多高性能的数据结构,例如未装箱的向量,而你的代码几乎没有变化。

答案 1 :(得分:4)

除了@leftaroundabout提到的更复杂的thunk问题之外,我可以想到两个算法的原因,为什么列表理解可能比你想象的更有效:

  • 由于懒惰,实际上只评估列表元素的一个。因此只执行一次取幂。
  • Haskell的^比简单地在循环中相乘更聪明:它在内部使用2除法来大大减少所需的乘法次数。例如。 x^8变为&#34;基本上&#34; let x2 = x*x; x4 = x2*x2 in x4*x4。 (编辑:正如@dfeuer指出的那样,这被称为&#34;快速取幂&#34;或通过平方&#34;&#34;取幂;)