微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Haskell中的foldR尾递归吗?

如何解决Haskell中的foldR尾递归吗?

我是 Haskell 的新手,从基本原理开始阅读 Haskell, 在 Folds 第 384 页中,我遇到了 FoldR 并且它似乎不是尾递归

foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)

1- 我们可以让它尾递归吗?

2-它会被优化吗?

解决方法

在像 Haskell 这样的懒惰语言中,尾递归通常是一个坏主意。这是其中一种情况。

我们可能会尝试通过例如 foldr 尾递归从 reverse 开始(也可以用尾递归方式),然后以尾递归方式逐步累积 foldr 结果。但是,这会破坏 foldr 语义。

使用标准 foldr,我们有例如

foldr (\_ _ -> k1) k2 (x:xs) = k1

无论 xs 是什么,包括像 undefined 这样的底值或像 [0..] 这样的无限列表。此外,当 xs 是一个有限但很长的列表时,上面的代码也是有效的,因为它会立即停止计算而无需扫描整个列表。

举一个更实际的例子,

and :: [Bool] -> Bool
and = foldr (&&) True

只要 and xs 的某个元素的计算结果为 False,就让 xs 返回 False,而不扫描列表的其余部分。

最后,将 foldr 转换为尾递归函数将:

  • 在处理部分定义的列表 (1:2:undefined) 或无限列表 ([0..]) 时更改语义;
  • 在有限长度列表上效率较低,即使不需要,也总是必须完全扫描。
,

foldr 不是尾递归......但它可用于编写在常量空间中处理列表的函数。 chi 已经指出它可以有效地实现 and。以下是它如何实现对列表求和的有效函数:

mySum :: Num a => [a] -> a
mySum xs = foldr go id xs 0
  where
    go x r acc = r $! x + acc

效果如何?考虑mySum [1,2,3]。这扩展为

foldr go id [1,3] 0
==> -- definition of foldr
go 1 (foldr go id [2,3]) 0
==> -- definition of go
foldr go id [2,3] $! 1 + 0
==> -- strict application
foldr go id [2,3] 1

我们将列表大小减少了 1,并且没有在“堆栈”上累积任何内容。重复相同的过程,直到我们得到

foldr go id [] 6
==> definition of foldr
id 6
==> definition of id
6

注意:如果此代码由 GHC 编译并启用优化(-O-O2),那么它实际上会将其转换为极快的尾递归代码,而无需您的任何进一步帮助。但即使未优化,它也能正常工作,不会烧掉一堆内存/堆栈。

,

struct ContentView: View { var body: some View { VStack(spacing: 4.0) { ForEach(1..<4) { Text(Array<String>(repeating: "word",count: $0).joined(separator: " ")) .frame(maxWidth: .infinity) /// keep the maxWidth .background(Color.gray) } } .fixedSize() /// here! } } 不是尾递归。 有时它被称为真正的“递归”折叠,而向左折叠是“迭代”(因为尾递归相当于迭代)。

请注意,在 Haskell 中,由于懒惰,foldr 也不保证空间恒定,这就是它存在的原因 foldl

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。