在 Haskell Wiki 的Recursion in a monad中,有一个声称是尾递归的例子:
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))
显然,这不是尾递归,恕我直言。原因是递归调用不是计算的结束。它执行多次,并将结果组合起来以形成最终结果。