【问题标题】:Can Strictness Guide Recursion?严格性可以指导递归吗?
【发布时间】:2013-07-28 09:33:15
【问题描述】:

假设我们在 Haskell 中有一个简单的树创建算法:

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)

makeTree :: Tree Int -> Tree Int
makeTree (Node 0 l r) = Node 0 EmptyTree EmptyTree
makeTree (Node n l r) = Node n (makeTree $ newTree (n - 1))
                               (makeTree $ newTree (n - 1))
  where
    newTree n = Node n EmptyTree EmptyTree

对于非常大的数字,我们预计该算法会因“堆栈大小溢出”错误而失败。这是因为该算法是二进制递归的,而不是尾递归的。我可以使用 bang 模式(在生成的左子树“!(makeTree $ newTree (n - 1))”上)将二进制递归引导为尾递归,因为由于严格性,现在应该引导递归?

编辑:

事实证明,真正的问题不是树的创建,而是消耗树的函数。还有一个函数用来展平树,其中的实例如下:

import qualified Data.Foldable as F

instance F.Foldable Tree where
    foldMap f EmptyTree = mempty
    foldMap f (Node x l r) = F.foldMap f l `mappend`
                             f x           `mappend`
                             F.foldMap f r

flatten = F.foldMap (:[])

因此调用了树的展平,并且可能在这里发生了溢出。如果是这样,解决方案是否像假设将 foldl 转换为 foldl' 一样简单?还是二进制折叠会增加额外的问题?

【问题讨论】:

  • 嗯?即使这是严格的,这些递归也不能是尾,因为尾调用必须是函数做的最后一件事。在这里,您还调用了两个递归之后的 Node 构造函数。
  • 这段代码无法编译
  • 如果您修复代码使其能够编译,则此函数不会发生堆栈溢出。懒惰有助于...
  • 我只是使用 ghci 来实现快速正确的功能,我假设我只需要一个 main 来编译代码。

标签: haskell recursion tail-recursion


【解决方案1】:

我假设您打算创建一棵非常深的树,例如

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)

makeTree :: Int -> Tree Int
makeTree 0 = EmptyTree
makeTree n = Node n (makeTree (n - 1)) (makeTree (n - 1))

关键是 Haskell 是懒惰的。因此,在调用该函数之后,实际上并没有创建任何内容,除了在需要时评估的 thunk。堆栈上没有分配任何内容,因为对makeTree 的调用不涉及递归调用。在检查根节点之后,调用递归调用,但同样只调用一级。这意味着检查每个节点只花费一些有限的时间和内存(在这种情况下是常数),而不取决于树的深度。重要的属性是每个递归调用都在构造函数中。这有时称为corecursion 或受保护的递归。

在使用树的函数中可能会发生堆栈溢出,但这是另一回事。

【讨论】:

  • 有趣,我想我做了一个愚蠢的假设,即创建树是问题所在。那么是的,我稍后会消耗这棵树(我把树弄平)。可折叠树的该实例位于已编辑的问题中。但是,为什么会出现折叠而不是树创建的 thunk(如果确实发生了这种情况)?是什么让这个 foldMap 实例与众不同?
  • @user1104160 把 Haskell 的数据想象成数学对象。当您“创建”它们时,它们不存在。它们只在 thunk 中处于休眠状态,就像我们想象中的数学对象一样。由于懒惰,Haskell 仅在需要时才开始创建它们。
  • 所以你的意思是我不应该通过展平该树来获得堆栈大小溢出,因为它像创建树一样遍历整个树,所以相同数量的“仅何时执行?
  • @user1104160 我会说这取决于你的树的深度。如果树真的很深,你可能会得到堆栈溢出异常。您能否发布一段导致异常的独立代码?
  • 树的高度为 18。
猜你喜欢
  • 1970-01-01
  • 2014-10-07
  • 2011-12-28
  • 1970-01-01
  • 2016-05-28
  • 2016-11-10
  • 2011-09-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多