4

Ross Paterson:箭头和计算介绍了trace函数(第 11 页):

trace :: ((a, c) -> (b, c)) -> a -> b
trace f a = let (b, c) = f (a, c) in b

trace函数对于模块化循环程序中的魔术反馈步骤很有用。例如,考虑 Richard Bird 的著名repmin函数,它找到一棵树的最小叶值创建一棵相同的树,每个叶值都被最小叶值替换,这两者都是通过巧妙地使用惰性求值和局部递归(如提供letrec):

data Tree = Leaf Int | Node Tree Tree deriving Show

repmin :: Tree -> Tree
repmin = trace repmin'

repmin' :: (Tree, Int) -> (Tree, Int)
-- put the minimum value m into the leaf and return the old value n as the minimum
repmin' (Leaf n, m) = (Leaf m, n)
-- copy the minimum value m into both the left and right subtrees and
-- set the minimum value m to the minimum of both the left and right subtrees
repmin' (Node l r, m) = let (l', lmin) = repmin' l m in
                        let (r', rmin) = repmin' r m in
                        (Node l' r', lmin `min` rmin)

无论如何,我想知道如何trace在 JavaScript 中实现该函数,以便我们可以实现repmin如下:

function Leaf(value) {
    this.value = value;
}

function Node(left, right) {
    this.left  = left;
    this.right = right;
}

var repmin = trace(function repmin(tree, min) {
    switch (tree.constructor) {
    case Leaf:
        return [new Leaf(min), tree.value];
    case Node:
        var [left,  lmin] = repmin(tree.left,  min);
        var [right, rmin] = repmin(tree.right, min);
        return [new Node(left, right), Math.min(lmin, rmin)];
    }
});

为了实现trace,我们需要提供的本地递归,letrec以便我们可以编写如下内容:

function trace(f) {
    return function (a) {
        var [b, c] = f(a, c);
        return b;
    };
}

我原本是想许下c诺言的。然而,这改变了trace. 那么,你能想出一种trace在 JavaScript 中实现而不改变其语义的方法吗?

4

1 回答 1

5

实际上,你只需要惰性求值,因为 JavaScript 中的赋值就像letrec. 惰性评估通常使用thunk实现。因此,您可以trace按如下方式实现:

function trace(f) {
    return function (a) {
        var [b, c] = f(a, () => c);
        return b;
    };
}

使用 的这个定义tracerepmin函数可以保持不变:

var repmin = trace(function repmin(tree, min) {
    switch (tree.constructor) {
    case Leaf:
        return [new Leaf(min), tree.value];
    case Node:
        var [left,  lmin] = repmin(tree.left,  min);
        var [right, rmin] = repmin(tree.right, min);
        return [new Node(left, right), Math.min(lmin, rmin)];
    }
});

但是,您可能希望使用 getter 使数据构造函数变得懒惰:

function Leaf(value) {
    Object.defineProperty(this, "value", descOf(value));
}

function Node(left, right) {
    Object.defineProperty(this, "left",  descOf(left));
    Object.defineProperty(this, "right", descOf(right));
}

function descOf(value) {
    var desc = { enumerable: true };
    var prop = typeof value === "function" &&
        value.length === 0 ? "get" : "value";
    desc[prop] = value;
    return desc;
}

把它们放在一起:

var tree = new Node(new Node(new Leaf(1), new Leaf(2)),
                    new Node(new Leaf(3), new Leaf(4)));

console.log("Input: ", show(tree));

console.log("Output:", show(repmin(tree)));

function show(tree) {
    switch (tree.constructor) {
    case Leaf: return "Leaf(" + tree.value + ")";
    case Node: return "Node(" + show(tree.left) + ", " + show(tree.right) + ")";
    }
}
<script>
function trace(f) {
    return function (a) {
        var [b, c] = f(a, () => c);
        return b;
    };
}

var repmin = trace(function repmin(tree, min) {
    switch (tree.constructor) {
    case Leaf:
        return [new Leaf(min), tree.value];
    case Node:
        var [left,  lmin] = repmin(tree.left,  min);
        var [right, rmin] = repmin(tree.right, min);
        return [new Node(left, right), Math.min(lmin, rmin)];
    }
});

function Leaf(value) {
    Object.defineProperty(this, "value", descOf(value));
}

function Node(left, right) {
    Object.defineProperty(this, "left",  descOf(left));
    Object.defineProperty(this, "right", descOf(right));
}

function descOf(value) {
    var desc = { enumerable: true };
    var prop = typeof value === "function" &&
        value.length === 0 ? "get" : "value";
    desc[prop] = value;
    return desc;
}
</script>

唯一的问题是您将无法编写如下函数:

var sqr = trace((x, y) => [y * y, x]);

这是因为*操作员并不懒惰。因此,您必须定义一个惰性mul函数:

var sqr = trace((x, y) => [mul(y, y), x]);

console.log(evaluate(sqr(10)));

function mul(a, b) {
    return function () {
        return evaluate(a) * evaluate(b);
    };
}

function evaluate(value) {
    return typeof value === "function" &&
        value.length === 0 ? value() : value;
}

function trace(f) {
    return function (a) {
        var [b, c] = f(a, () => c);
        return b;
    };
}

希望有帮助。

于 2016-10-30T16:08:02.990 回答