Haskell 尾递归内部函数

Posted

技术标签:

【中文标题】Haskell 尾递归内部函数【英文标题】:Haskell Tail Recursive Inner Functions 【发布时间】:2015-05-09 04:51:30 【问题描述】:

来自 Scala 背景,我非常习惯于使用尾递归来编写无法使用其他方法轻松表示的内容。假设我想计算列表的长度,但我不想使用折叠。我想以自然的尾递归方式来做。我是这样想的:

myLength :: [a]  -> Int
myLength x    = innerLength x 0

innerLength :: [a] -> Int -> Int  -- Tail recursive inner call
innerLength [] _      = 0
innerLength (x:xs) n  = innerLength xs n+1

这很好用。但是,innerLength 实际上是用于计算列表成员的尾递归内部调用并不是很可读,而且似乎 innerLength 的作用域是这样的,有人可以只使用 innerLength 而不是更好的 myLength。

有没有更好的方法来做到这一点?

【问题讨论】:

是的,在myLength 的定义中使用letwhere 子句。 innerLength xs n+1 被解析为(innerLength xs n)+1,这是不是尾递归的。 @Franky 所说的,而且innerLength [] _ = 0 扔掉了累加器而不是返回它。应该是innerLength [] n = n。我也很想在递归情况下使用严格的应用程序,例如innerLength (_:xs) n = innerLength xs $! n+1 @Franky 感谢您指出这一点。对于使用 haskell 的我来说,这似乎一直是一个令人头疼的问题。 【参考方案1】:

是的,您可以使用let

myLength :: [a]  -> Int
myLength x = 
    let innerLength :: [a] -> Int -> Int  -- Tail recursive inner call
        innerLength [] n      = n
        innerLength (x:xs) n  = innerLength xs (n+1)
    in innerLength x 0

Live demo

where:

myLength :: [a]  -> Int
myLength x = innerLength x 0
    where innerLength :: [a] -> Int -> Int  -- Tail recursive inner call
          innerLength [] n      = n
          innerLength (x:xs) n  = innerLength xs (n+1)

Live demo

两者的区别见here。

【讨论】:

好吧,innerLength [] n = n,真的。 Haskell 风格通常将辅助函数命名为“go”或“aux”,因为它的范围仅限于函数,而替代方法只是重复稍微修改的函数名称,这不会带来任何可读性的好处。只有顶层函数通常得到显式签名,这被认为是局部变量足够接近潜在问题,它们不需要一个(除非你想澄清一个特别晦涩的类型)。

以上是关于Haskell 尾递归内部函数的主要内容,如果未能解决你的问题,请参考以下文章

尾调用优化和尾递归改写

Python进阶 —— 尾递归

递归函数

有啥能阻止优化尾递归?

内部有循环的递归函数

如何在函数内部使用递归查询?