【发布时间】:2012-11-14 12:44:44
【问题描述】:
在 Haskell Wiki 的 Recursion in a monad 中有一个示例,声称是 tail-recursive:
f 0 acc = return (reverse acc)
f n acc = do
v <- getLine
f (n-1) (v : acc)
虽然命令式表示法让我们相信它是尾递归的,但它一点也不明显(至少对我而言)。如果我们去糖 do 我们得到
f 0 acc = return (reverse acc)
f n acc = getLine >>= \v -> f (n-1) (v : acc)
重写第二行导致
f n acc = (>>=) getLine (\v -> f (n-1) (v : acc))
所以我们看到f 出现在>>= 的第二个参数中,而不是在尾递归位置。我们需要检查IO 的>>= 以获得答案。
显然将递归调用作为do 块中的最后一行并不是尾递归函数的充分条件。
假设 monad 是尾递归的当且仅当此 monad 中的每个递归函数都定义为
f = do
...
f ...
或等效
f ... = (...) >>= \x -> f ...
是尾递归的。我的问题是:
- 哪些单子是尾递归的?
- 是否有一些通用规则可以用来立即区分尾递归单子?
更新:让我举一个具体的反例:根据上述定义,[] monad 不是尾递归的。如果是的话,那么
f 0 acc = acc
f n acc = do
r <- acc
f (n - 1) (map (r +) acc)
必须是尾递归的。但是,对第二行进行脱糖会导致
f n acc = acc >>= \r -> f (n - 1) (map (r +) acc)
= (flip concatMap) acc (\r -> f (n - 1) (map (r +) acc))
显然,这不是尾递归,恕我直言,无法做到。原因是递归调用不是计算的结束。多次执行,将结果合并为最终结果。
【问题讨论】:
-
请注意:是尾递归的。尾递归只是意味着函数没有使用最后一次函数调用的返回值。在您的情况下,未使用最终
f调用的值。如果您宁愿务实地考虑它,函数是尾递归的,如果在执行最后一次调用后,您可以处理与函数关联的所有上下文。此外,据我所知,任何单子都没有任何固有的尾递归或非尾递归。 -
@scvalex 虽然直觉上这是有道理的,但我想正式证明它是合理的。根据Tail recursion 中所述的标准,您能否证明
f是尾递归的? -
@hammar 该定义并不关心您是否可以定义不是该形式的递归函数。它只关心任意函数的形式是否为尾递归。
-
不能直接内联
>>=的定义,看看结果是不是尾递归的? -
@PetrPudlák 查看该页面上的定义,我不得不说
f不是尾递归的。另一方面,我不同意这个定义,因为它似乎排除了调用任何其他函数作为扩展函数的第一步。根据该定义,f = f $ 1不是尾递归的。
标签: haskell monads tail-recursion