flatten'是尾递归的,所以它不应该占用任何堆栈空间。然而,它会沿着树的左侧向下走,在堆中吐出一堆 thunk。如果您在示例树上调用它,并将其简化为 WHNF,您应该得到如下所示的内容:
:
/ \
1 flatten' Tip :
/ \
2 flatten' (Node 4) []
/ \
(Node 3) Tip
/ \
Tip Tip
该算法是O(N),但它必须检查Tips 以及Nodes。
这看起来是按顺序展平树的最有效方法。该Data.Tree模块在这里flatten有一个函数,它做很多相同的事情,除了它更喜欢前序遍历。
更新:
在图形缩减引擎中,flatteninmain将生成如下图:
@
/ \
@ []
/ \
/ \
/ \
flatten' Node 2
/ \
/ \
/ \
Node 1 Node 4
/ \ / \
Tip Tip / \
/ \
Node 3 Tip
/ \
Tip Tip
为了将其简化为 WHNF,图形缩减引擎将展开脊椎,将[]和推Node 2到堆栈上。然后它将调用该flatten'函数,该函数会将图形重写为:
@
/ \
/ \
/ \
@ :
/ \ / \
/ \ 2 \
/ \ \
flatten' Node 1 \
/ \ \
Tip Tip @
/ \
@ []
/ \
/ \
/ \
flatten' Node 4
/ \
/ \
/ \
Node 3 Tip
/ \
Tip Tip
并将从堆栈中弹出两个参数。根节点仍然不在 WHNF 中,因此图形缩减引擎将展开脊椎,将2:...和Node 1推入堆栈。然后它将调用该flatten'函数,该函数会将图形重写为:
@
/ \
/ \
/ \
@ :
/ \ / \
/ \ 1 \
/ \ \
flatten' Tip @
/ \
/ \
/ :
@ / \
/ \ 2 \
/ Tip @
/ / \
flatten' @ []
/ \
/ \
/ \
flatten' Node 4
/ \
/ \
/ \
Node 3 Tip
/ \
Tip Tip
并将从堆栈中弹出两个参数。根节点仍然不在 WHNF 中,因此图形缩减引擎将展开脊椎,将1:...和Tip推入堆栈。然后它将调用该flatten'函数,该函数会将图形重写为:
:
/ \
1 \
\
@
/ \
/ \
/ :
@ / \
/ \ 2 \
/ Tip @
/ / \
flatten' @ []
/ \
/ \
/ \
flatten' Node 4
/ \
/ \
/ \
Node 3 Tip
/ \
Tip Tip
并将从堆栈中弹出两个参数。我们现在处于 WHNF 中,最多消耗了两个堆栈条目(假设Tree节点不是需要额外堆栈空间来评估的 thunk)。
所以,flatten' 是尾递归的。它无需评估额外的嵌套 redexes 即可替换自身。第二个flatten'仍然是堆中的一个 thunk,而不是堆栈。