如何实现尾递归列表追加?
像这样的简单追加函数(在 F# 中):
let rec app s t =
match s with
| [] -> t
| (x::ss) -> x :: (app ss t)
当 s 变大时将会崩溃,因为该函数不是尾递归的。我注意到 F# 的标准追加函数不会因大列表而崩溃,因此必须以不同的方式实现。所以我想知道:append 的尾递归定义是什么样的?我想出了这样的方法:
let rec comb s t =
match s with
| [] -> t
| (x::ss) -> comb ss (x::t)
let app2 s t = comb (List.rev s) t
它有效,但看起来很奇怪。有没有更优雅的定义?
A simple append function like this (in F#):
let rec app s t =
match s with
| [] -> t
| (x::ss) -> x :: (app ss t)
will crash when s becomes big, since the function is not tail recursive. I noticed that F#'s standard append function does not crash with big lists, so it must be implemented differently. So I wondered: How does a tail recursive definition of append look like? I came up with something like this:
let rec comb s t =
match s with
| [] -> t
| (x::ss) -> comb ss (x::t)
let app2 s t = comb (List.rev s) t
which works, but looks rather odd. Is there a more elegant definition?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
传统(非尾递归)
使用累加器(尾递归)
使用延续(尾递归)
转换起来非常简单任何非尾递归函数都优于带有延续的递归函数,但我个人更喜欢累加器,因为它具有直接的可读性。
Traditional (not tail-recursive)
With an accumulator (tail-recursive)
With continuations (tail-recursive)
Its pretty straight-forward to convert any non-tail recursive function to recursive with continuations, but I personally prefer accumulators for straight-forward readability.
除了 Juliet 发布的内容之外:
使用序列表达式
在内部,序列表达式生成尾递归代码,因此这工作得很好。
使用可变 .NET 类型
David 提到 F# 列表可以发生变化 - 但这仅限于 F# 核心库(并且用户无法使用该功能,因为它破坏了功能概念)。您可以使用可变的 .NET 数据类型来实现基于突变的版本:
这在某些情况下可能很有用,但我通常会避免 F# 代码中的突变。
In addition to what Juliet posted:
Using sequence expressions
Internally, sequence expressions generate tail-recursive code, so this works just fine.
Using mutable .NET types
David mentioned that F# lists can be mutated - that's however limited only to F# core libraries (and the feature cannot be used by users, because it breaks the functional concepts). You can use mutable .NET data types to implement a mutation-based version:
This may be useful in some scenarios, but I'd generally avoid mutation in F# code.
快速浏览一下 F# 源代码,尾部似乎在内部是可变的。一个简单的解决方案是在将第一个列表的元素连接到第二个列表之前反转第一个列表。加上反转列表,以递归方式实现尾部是微不足道的。
From a quick glance at the F# sources, it seems the tail is internally mutable. A simple solution would be to reverse the first list before consing its elements to the second list. That, along with reversing the list, are trivial to implement tail recursively.