每个 Haskell 函数都进行尾调用吗?
Does every Haskell function do tail calls?
我想 Haskell 中的每个函数都应该是尾递归的。
作为非尾递归函数实现的阶乘函数:
fact 0 = 1
fact n = n * fact (n - 1)
每个运算符也是一个函数,所以这等同于
fact 0 = 1
fact n = (*) n (fact (n - 1))
但这分明是给我的尾声。我想知道如果每次调用都在堆上创建一个新的 thunk,为什么这段代码会导致堆栈溢出。我不应该得到堆溢出吗?
代码中
fact 0 = 1
fact n = (*) n (fact (n - 1))
正如您观察到的,最后一个 (*) ...
是尾调用。然而,最后一个参数 fact (n-1)
将构建一个 (*)
立即要求的 thunk。这导致对 fact
的非尾调用。递归地,这将消耗堆栈。
TL;DR:发布的代码执行了尾调用,但 (*)
没有。
(另外 Haskell 中的 "the stack" 是一个不像严格语言中那么清晰的概念。Haskell 的某些实现使用比这更复杂的东西。您可以搜索 "push/enter vs eval/apply" 策略,如果你想要一些血淋淋的细节。)
我想 Haskell 中的每个函数都应该是尾递归的。
作为非尾递归函数实现的阶乘函数:
fact 0 = 1
fact n = n * fact (n - 1)
每个运算符也是一个函数,所以这等同于
fact 0 = 1
fact n = (*) n (fact (n - 1))
但这分明是给我的尾声。我想知道如果每次调用都在堆上创建一个新的 thunk,为什么这段代码会导致堆栈溢出。我不应该得到堆溢出吗?
代码中
fact 0 = 1
fact n = (*) n (fact (n - 1))
正如您观察到的,最后一个 (*) ...
是尾调用。然而,最后一个参数 fact (n-1)
将构建一个 (*)
立即要求的 thunk。这导致对 fact
的非尾调用。递归地,这将消耗堆栈。
TL;DR:发布的代码执行了尾调用,但 (*)
没有。
(另外 Haskell 中的 "the stack" 是一个不像严格语言中那么清晰的概念。Haskell 的某些实现使用比这更复杂的东西。您可以搜索 "push/enter vs eval/apply" 策略,如果你想要一些血淋淋的细节。)