如何从一个纯JavaScript函数中恢复源代码?

4

在λ演算中,“纯”的含义是指仅包含单参数函数和单参数函数调用的单参数函数。恢复源代码的意思是指变量重命名后的代码与原代码相同。例如,

n2 = function(v0){return function(v1){return v0(v0(v1))}}
console.log(source(n2));
console.log(source(n2(n2)));

应该打印:

function(v0){return function(v0){return v0(v0(v1))}}
function(v0){return function(v0){return v0(v0(v0(v0(v1))))}}

也就是说,第一行显示了函数 n2 的原始来源,而第二行显示了通过评估 n2(n2) 返回的函数的来源。

我已经成功地实现了它,如下所示:

function source(f){
    var nextVarId = 0;
    return (function recur(f){
        if (typeof f === "function"){
            if (f.isVarFunc) return f(null);
            else {
                var varName = "v"+(nextVarId++);
                var varFunc = function rec(res){
                    var varFunc = function(arg){
                        return arg === null
                            ? "("+res.join(")(")+")"
                            : rec(res.concat(recur(arg)));
                    };
                    varFunc.isVarFunc = true;
                    return varFunc;
                };
                varFunc.isVarFunc = true;
                var body = f(varFunc([varName]));
                body     = body.isVarFunc ? body(null) : recur(body);
                return "(function("+varName+"){return "+body+"})";
            };
        } else return f;
    })(f);
};

问题在于我使用了一种相当丑陋的方法来标记函数,即将它们的名称设置为特定值,而这种方法不适用于应用多次的函数(例如a(b)(b))。有没有更好的方法来解决这个问题?
编辑:我成功设计了一个版本,似乎在所有情况下都是正确的,但它仍然是一个丑陋、难以阅读和缺乏原则性的混乱。

这不是闭包的工作方式。创建对封闭范围的引用,代码保持不变。 - Bergi
你可能更希望在JS中实现自己的lambda演算解释器(这并不复杂),而不是去破坏JS函数和JS代码。 - Bergi
代码的目的是强制V8引擎将JavaScript函数评估为正常形式,这意味着每个闭包都会折叠。 - MaiaVictor
1
我已经在JavaScript上编写了几个λ演算解释器,实际上,其中一个渐进地比V8本身更快(Optlam),但在许多情况下,你根本无法使解释器运行得比本地函数更快,因此我特别需要我所要求的东西。 - MaiaVictor
3
你的问题在于 n2(n2) 创建了一个JS闭包函数,而不是折叠函数,因此没有提供对其内部(闭合变量,我的意思是代码不足)的任何访问。当然,你可以编写自己的 apply(n2, [n2]) 函数来创建正常形式的函数。 - Bergi
好的,我现在正在考虑那种方法。 - MaiaVictor
1个回答

0

最后,这是上面混乱版本的相当清理过的版本。

// source :: PureFunction -> String
// Evaluates a pure JavaScript function to normal form and returns the 
// source code of the resulting function as a string.
function source(fn){
    var nextVarId = 0;
    return (function normalize(fn){
        // This is responsible for collecting the argument list of a bound
        // variable. For example, in `function(x){return x(a)(b)(c)}`, it
        // collects `a`, `b`, `c` as the arguments of `x`.  For that, it
        // creates a variadic argumented function that is applied to many
        // arguments, collecting them in a closure, until it is applied to
        // `null`. When it is, it returns the JS source string for the
        // application of the collected argument list.
        function application(argList){
            var app = function(arg){
                return arg === null
                    ? "("+argList.join(")(")+")"
                    : application(argList.concat(normalize(arg)));
            };
            app.isApplication = true;
            return app;
        };
        // If we try to normalize an application, we apply
        // it to `null` to stop the argument-collecting.
        if (fn.isApplication) 
            return fn(null);
        // Otherwise, it is a JavaScript function. We need to create an
        // application for its variable, and call the function on it.
        // We then normalize the resulting body and return the JS
        // source for the function.
        else {
            var varName = "v"+(nextVarId++);
            var body    = normalize(fn(application([varName])));
            return "(function("+varName+"){return "+body+"})";
        };
    })(fn);
};

虽然还不完美,但看起来好多了。它按预期工作:

console.log(source(function(a){return function(b){return a(b)}}))

输出:

(function(v0){return (function(v1){return (v0)((v1))})})

我不知道那有多低效。


2
这段代码真的应该是对原问题的编辑。顺便说一下,如果你解释一下为什么想要做这些事情,你可能会得到更多的回应。(顺便提一句,我是通过你在SO Meta上的问题来到这里的)。 - PM 2Ring
4
如果这个回答能够解决原问题,那么为什么它应该是一次编辑而不是一个答案呢? - BSMP
1
@BSMP:作为一个回答,它是“可以的”,但在我看来,它更适合放在问题中,因为它更像是Viclib解决问题的进展报告,并且提到它并没有完全解决原始问题,因为Viclib仍然有关于这种方法效率的问题。但我肯定不会对它进行负评或标记删除! - PM 2Ring
我发了帖子,因为如果这个问题的范围不涉及效率方面,那么这就是实际答案。但是,我在问题上搞砸了。 - MaiaVictor
我希望这可以强制评估纯JavaScript函数,以避免thunk无限增长。 - MaiaVictor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接