为什么在Haskell的Data.List中有两个'reverse'定义

时间:2014-06-24 15:49:51

标签: haskell

查看定义为

source of Data.List节目reverse
#ifdef USE_REPORT_PRELUDE
reverse                 =  foldl (flip (:)) []
#else
reverse l =  rev l []
  where
    rev []     a = a
    rev (x:xs) a = rev xs (x:a)
#endif

我想知道为什么提供第二个定义?在某种意义上它是否优越?

编辑:

as @ n.m。下面的评论,第二个版本“就像第一个版本的直接重写,foldl已扩展,flip (:)内联。”实际上,Data.List本身将foldl定义为

foldl        :: (b -> a -> b) -> b -> [a] -> b
foldl f z0 xs0 = lgo z0 xs0
             where
                lgo z []     =  z
                lgo z (x:xs) = lgo (f z x) xs

不可能知道Data.List的作者的动机(除非其中一个碰巧访问此页面),但作为一个初学者,如果我编写像reverse的第一个版本的代码就可以了并让编译器为我做内联?

2 个答案:

答案 0 :(得分:11)

我相信第一个版本,

reverse                 =  foldl (flip (:)) []

Haskell报告中定义的reverse版本,第二个版本

reverse l =  rev l []
  where
    rev []     a = a
    rev (x:xs) a = rev xs (x:a)

是一种效率更高的等效函数。你可以看到第二个版本使用了一个accum参数,所以整个事情都是一个尾调用,这在大多数Haskell实现上非常有效。

第二个版本是默认提供的版本,也许提供第一个版本,以便编译器编写者可以使用报告中更简洁的函数定义来测试程序的执行情况。

注意:似乎其他人在a post to haskell-cafe得出了同样的结论。

答案 1 :(得分:1)

我们可以编译这两个版本的副本,看看ghc输出了什么。

module Rev where

myReverse1                 =  foldl (flip (:)) []
myReverse2 l =  rev l []
  where
    rev []     a = a
    rev (x:xs) a = rev xs (x:a)

使用-ddump-simpl构建以查看生成的核心,并-dsuppress-all消除一些无关的噪音:

rwbarton@morphism:/tmp$ ghc -O -ddump-simpl -dsuppress-all -fforce-recomp Rev
[1 of 1] Compiling Rev              ( Rev.hs, Rev.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 40, types: 58, coercions: 0}

Rec {
myReverse3
myReverse3 =
  \ @ a_aNh z_aNB ds_aNC ->
    case ds_aNC of _ {
      [] -> z_aNB;
      : x_aNH xs_aNI -> myReverse3 (: x_aNH z_aNB) xs_aNI
    }
end Rec }

myReverse1
myReverse1 = \ @ a_aNh xs0_aNz -> myReverse3 ([]) xs0_aNz

Rec {
myReverse4
myReverse4 =
  \ @ a_aMV ds_dNu a1_auj ->
    case ds_dNu of _ {
      [] -> a1_auj;
      : x_auk xs_aul -> myReverse4 xs_aul (: x_auk a1_auj)
    }
end Rec }

myReverse2
myReverse2 = \ @ a_aMV l_auh -> myReverse4 l_auh ([])

myReverse3myReverse4的检查表明它们是相同的,除了它们以相反的顺序接受它们的论证。实际上,您可以看到lgofoldl的参数与rev中的myReverse2相反。由于这一点,我很确定没有明显的性能差异,如果它是无意的。

所以,是的,通过优化,GHC会将reverse的两个定义编译成基本相同的东西。我猜测内联定义存在的原因是

  • 很久以前大多数标准库的实现都是在GHC,Hugs和其他一些Haskell编译器之间共享的。也许GHC或其他系统之一在当时的优化方面并不是那么擅长。

  • 在进行GHC开发时,拥有这些手动优化版本仍然有点用处:通常在构建编译器及其库时禁用优化(因为它明显更快),然后手动优化这个意味着生成的编译器及其生成的程序效率更高实际上,我检查过,常见的“快速”BuildFlavour仍然使用-O构建库,所以没有太多真相毕竟。