有多种递归映射树的方法:
const reduceTree = (f, node) => {
const go = ([x, xs]) => f(x, xs.map(go));
return go(node);
};
const mapTree = (f, node) =>
reduceTree((x, xs) => Node_(f(x), xs), node);
const Node_ = (x, xs) => ([x, xs]);
const Node = (x, ...xs) => ([x, xs]);
const tree = Node(1,
Node(2,
Node(3),
Node(4,
Node(5))),
Node(6),
Node(7,
Node(8),
Node(9),
Node(10),
Node(11)));
console.log(
mapTree(x => 2 * x, tree));
实际上,这是一个不错的解决方案,但是,它不是堆栈安全的。由于递归调用(
xs.map(go)
)不在尾部位置,因此我不能退回到蹦床,而且以尾部递归形式转换算法对我来说似乎也不是一件容易的事。这样做的通常方法可能是CPS转换,这种转换会混淆计算。也许还有另一种实现堆栈安全的方法-例如使用发电机?是否存在可以以几乎机械方式应用的这种转换的一般规则?
我主要对进行此转换的过程感兴趣,而不对最终结果感兴趣。
最佳答案
从格式良好的相互递归对开始-
// map :: (a -> b, Tree a) -> Tree b
const map = (f, [ v, children ]) =>
Node (f (v), ...mapAll (f, children))
// mapAll :: (a -> b, [ Tree a ]) -> [ Tree b ]
const mapAll = (f, nodes = []) =>
nodes .map (n => map (f, n))
我们首先删除渴望的
Array.prototype.map
,它可能不允许尾部形式-const map = (f, [ v, children ]) =>
Node (f (v), ...mapAll (f, children))
const mapAll = (f, [ node, ...more ]) =>
node === undefined
? []
: [ map (f, node), ...mapAll (f, more) ]
接下来添加用于CPS转换的参数-
const identity = x =>
x
const map = (f, [ v, children ], k = identity) =>
mapAll (f, children, newChilds => // tail
k (Node (f (v), ...newChilds))) // tail
const mapAll = (f, [ node, ...more ], k = identity) =>
node === undefined
? k ([]) // tail
: map (f, node, newNode => // tail
mapAll (f, more, moreChilds => // tail
k ([ newNode, ...moreChilds ]))) // tail
在下面的您自己的浏览器中验证结果
const Node = (x, ...xs) =>
[ x, xs ]
const identity = x =>
x
const map = (f, [ v, children ], k = identity) =>
mapAll (f, children, newChilds =>
k (Node (f (v), ...newChilds)))
const mapAll = (f, [ node, ...more ], k = identity) =>
node === undefined
? k ([])
: map (f, node, newNode =>
mapAll (f, more, moreChilds =>
k ([ newNode, ...moreChilds ])))
const tree =
Node
( 1
, Node
( 2
, Node (3)
, Node
( 4
, Node (5)
)
)
, Node (6)
, Node
( 7
, Node (8)
, Node (9)
, Node (10)
, Node (11)
)
)
console.log (map (x => x * 10, tree))
请注意,CPS本身并不会使程序具有堆栈安全性。这只是将代码放在您选择的蹦床上的形式。