Haskell 哈斯克尔蓄能器

Haskell 哈斯克尔蓄能器,haskell,tail-recursion,Haskell,Tail Recursion,在哈斯克尔,如果我写 fac n = facRec n 1 where facRec 0 acc = acc facRec n acc = facRec (n-1) (acc*n) 然后用GHC编译,结果会和我用的有什么不同吗 fac 0 = 1 fac n = n * fac (n-1) 我可以很容易地做fac n=product[1..n]并避免整个过程,但我感兴趣的是尾部递归的尝试如何在惰性语言中工作。我知道我仍然可以得到堆栈溢出,因为thunks正在累积

在哈斯克尔,如果我写

 fac n = facRec n 1
   where facRec 0 acc = acc
         facRec n acc = facRec (n-1) (acc*n)
然后用GHC编译,结果会和我用的有什么不同吗

 fac 0 = 1
 fac n = n * fac (n-1)

我可以很容易地做
fac n=product[1..n]
并避免整个过程,但我感兴趣的是尾部递归的尝试如何在惰性语言中工作。我知道我仍然可以得到堆栈溢出,因为thunks正在累积,但是当我使用累加器时,与我只声明朴素递归时,实际发生的情况是否有所不同(就生成的编译程序而言)?除了提高易读性之外,省去尾部递归还有什么好处吗?如果我使用
runhaskell
来运行计算,而不是先编译它,答案是否会改变?

您的问题不完整。我假设您指的是GHC,至少在没有优化的情况下,答案是“是”,因为辅助函数(第一个是
facRec
,第二个是
fac
)与一相比有一个算术数2,程序集将反映这一点。对于优化或JHC,答案可能是“否”。

在haskell中,只有在累加器严格且需要计算整个结果的情况下,以尾部递归方式编写程序才有帮助

使用ghc的runHaskell,程序不会得到优化,因此不会进行严格的分析,因此可能会出现堆栈溢出;而如果使用优化进行编译,编译器可能会检测到累加器需要严格,并相应地进行优化

要了解事情发生的方式是否不同,最好的方法是检查生成的核心语言。顺便说一句,如果您对性能感兴趣,那么他的许多博文都很有趣。

如果累加器是严格的,那么尾部递归在(GHC)Haskell中是有意义的。为了说明这个问题,这里是对
fac
的尾部递归定义的“跟踪”:

   fac 4
~> facRec 4 1
~> facRec 3 (1*4)
~> facRec 2 ((1*4)*3)
~> facRec 1 (((1*4)*3)*2)
~> facRec 0 ((((1*4)*3)*2)*1)
~> (((1*4)*3)*2) * 1
  ~> ((1*4)*3) * 2
    ~> (1*4) * 3
      ~> 1*4
    ~> 4 * 3
  ~> 12 * 2
~> 24 * 1
~> 24
缩进级别(大致)对应于堆栈级别。请注意,累加器仅在最末端进行计算,这可能会导致堆栈溢出。当然,诀窍是使累加器严格。如果在严格的上下文中调用facRec,理论上可以证明它是严格的,但我不知道有哪个编译器会这样做,ATM。不过,GHC确实进行尾部调用优化,因此
facRec
调用使用恒定的堆栈空间

出于同样的原因,
foldl'
通常优于
foldl
,因为前者对累加器要求严格


关于您的第二部分,
runhaskell
/
runghc
只是GHCi的包装器。如果GHCi找到编译后的代码,它将使用编译后的代码,否则它将使用字节码解释器,该解释器执行很少的优化,因此不要期望发生任何奇特的优化。

您对“任何事情实际发生的方式是否不同”的定义是什么?简单的答案是“是”,因为它们是不同的——一个是尾部递归,另一个不是。但我不认为这就是你要问的….?你是不是说facRec n acc=facRec(n-1)(acc*n)??@lijie-我主要感兴趣的是GHC是否进行尾部调用优化(使用或不使用累加器),但我把它放在一般的位置,因为我不确定尾部递归如何与惰性语言交互,它可能会做其他不同的事情,基于我使用的是一种形式还是另一种形式。“我不知道那些东西是什么。”大卫·V.-啊。是的。固定的。感谢您的关注。我认为可以肯定地说,所有运行代码的函数式语言解释器/编译器/任何东西都可以实现尾部调用优化。但是,是的,累加器和懒惰的相互作用令人恼火……对不起,是的,我说的是GHC;修正了问题。另外,facRec可能应该在where子句中,而不是在顶级声明中。这是一个工作者包装器转换,它更可能被严格性分析器注意到。@stephen-在问题中修复。
foldl'
vs
fold
提示,我认为有帮助。换句话说,如果我想要从Scheme中习惯的尾部递归,我必须在seq acc'$facRec(n-1)acc'中说
let acc'=acc*n,而不是
facRec(n-1)(acc*n)
的最后一行
facRec
@Inaimathi:yes;或者,更简单地说,您可以编写
fracRec(n-1)$!acc*n
,其中
$
只是严格的函数应用程序(
f$!x=x`seq`fx
)。增加严格性的最简单方法是使用
{-#语言模式}
。然后就有了严格的模式匹配。例如,
let!x=。。。在…
中或直接在函数定义中:
facRec 0!n=…