部分求值只能传播定义是已知的绑定。在 Guile 中,这将内联限制于词法引用和
原语
引用,并明确地排除了全局引用、模块导入或可变对象的字段。因此我们还没有跨模块的内联,除了那些滥用宏展开器的黑客技巧外。
Partial evaluation can only propagate bindings whose definitions are known. In the case of Guile, then, that restricts inlining to lexical references and primitive references, and notably excludes global references and module imports, or fields of mutable objects. So this does not yet give us cross-module inlining, beyond the hacks that abuse the macro expander.
这个观察有一个推论,即某些语言提倡一种难以分析的编程风格。我这里实际是在讨论面向对象的语言,特别是动态的面向对象语言。当你在 Java 中看到 o.foo(),至少有可能 foo 是一个 final 方法,所以你知道如果你选择这么做,你可以内联它。但在 JavaScript 中,如果你看到 o.foo(),你不知道任何信息:在运行时,o 的属性集可以,而且确实会变化,因为人们会对对象 o、它的原型,或 Object.prototype 做
猴子补丁
。在大多数 JS 实现中,你甚至可以改变 o.__proto__。即使你能看到你的 o.foo() 调用是由 o.foo = ... 赋值的,在 ES5 中你仍然不知道任何信息,因为 o 可能有一个 foo 属性的 setter。
This observation has a correlary, in that some languages promote a style of programming that is difficult to analyze. I'm really talking about object-oriented languages here, and the dynamic ones in particular. When you see o.foo() in Java, there is at least the possibility that foo is a final method, so you know you can inline it if you choose to. But in JavaScript if you see o.foo(), you don't know anything: the set of properties of o can and does vary at runtime as people monkey-patch the object o, its prototype, or Object.prototype. You can even change o.__proto__ in most JS implementations. Even if you can see that your o.foo() call is dominated by a o.foo = ... assignment, you still don't know anything in ES5, as o could have a setter for the foo property.
有几件事在 JavaScript 世界中减轻了这种不利情况。
This situation is mitigated in the JavaScript world by a couple of things.
首先,你不必以这种方式编程:你可以以一种更函数式的风格使用词法作用域。加上
严格模式
,这让编译器可以看到对 foo 的调用可以被内联,只要 foo 在源程序中不是可变的。这是一个可以通过静态分析轻松证明的属性。
First of all, you don't have to program this way: you can use lexical scoping in a more functional style. Coupled with strict mode, this allows a compiler to see that a call to foo can be inlined, as long as foo isn't mutated in the source program. That is a property that is cheap to prove statically.
然而,如 Andreas Gal 发现的那样,这不是主流 JS 实现做的事。这真的是一种遗憾,对我们所写的程序产生了持久的影响。
However, as Andreas Gal found out, this isn't something that the mainstream JS implementations do. It is really a shame, and it has a lasting impact on the programs we write.
我甚至听到一些人说,在 JavaScript 中你应该避免深层词法绑定,因为访问时间取决于绑定的深度。虽然这对于当前的实现是对的,但这是实现的属性,而不是语言本身的属性。在严格模式中,不使用 with 和 eval 引入的绑定,这样可以快速计算每个函数表达式的自由变量集。当
闭包
被创建时,JS 的实现可以不使用某种嵌套作用域的
句柄
,而是直接复制自由变量的值,并将它们存储在与函数代码相关联的
向量
中(你看,闭包是包含数据的代码)。接着对这些变量的任何访问都通过向量而不是作用域对象进行。
I even heard a couple people say that in JS, you should avoid deep lexical bindings, because the access time depends on the binding depth. While this is true for current implementations, it is a property of the implementations and not of the language. Absent with and eval-introduced bindings, a property that is true in strict-mode code, it is possible to quickly compute the set of free variables for every function expression. When the closure is made, instead of grabbing a handle on some sort of nested scope object, a JS implementation can just copy the values of the free variables, and store them in a vector associated with the function code. (You see, a closure is code with data.) Then any accesses to those variables go through the vector instead of the scope.
对于被赋值的变量⸺再强调一次,这是一个可以静态证明的属性⸺你把变量放到一个新的“盒子”中,并重写对这些变量的访问,使其通过这个盒子进行。捕获一个自由变量时复制的是那个盒子,而不是它的值。
For assigned variables -- again, a property that can be proven statically -- you put the variables in a fresh "box", and rewrite accesses to those variables to go through that box. Capturing a free variable copies the box instead of its value.
这个技术没有什么新东西;Cardelli 和 Dybvig(也可能是其他人)在 80 年代各自独立发现。
There is nothing new about this technique; Cardelli and Dybvig (and probably others) discovered it independently in the 80s.
关于闭包实现的这一点与部分求值有关:人们不会太抱怨 JS 中糟糕的静态内联器,因为普遍差劲的闭包实现损害了词法抽象。真是遗憾!
This point about closure implementation is related to partial evaluation: people don't complain much about the poor static inliners of JS, because the generally poor closure implementations penalize lexical abstraction. Truly a shame!