JavaScript中动态类型运算符的双重调度

Double dispatch for dynamically typed operators in JavaScript

本文关键字:调度 运算符 动态 类型 JavaScript      更新时间:2023-12-19

我想用这样一种方式来公式化代数表达式,即可以交换底层的数字类型。如果你想的话,想想复数、大整数、矩阵等等。出于这个原因,我会写add(a, b)a.add(b)而不是a + b。在静态类型语言中,我只需使用函数add的基于类型的重载来实现各种替代方案。但对于JavaScript来说,这不起作用,所以我正在寻找替代方案。执行的方法取决于两个操作数的类型。

我想到的一种方法是以下双重调度机制:

  1. 将表达式写成a.add(b)

  2. 对于给定的类型(例如,我自己的Complex类型或内置的Number类型),可以通过以下方式实现该方法:

    add: function(that) { that.addComplex(this); }
    

    因此,第二个调用的方法名对其中一个操作数的类型进行编码。

  3. 采用专门的方法来处理所有组合。例如,设置

    Number.prototype.addComplex = function(that)
      { return newComplex(that.real + this, that.imaginary); }
    

假设我知道所有类型,这样我就可以确保处理所有组合。现在让我困扰的是这些对象的创建

上面的方法在很大程度上依赖于虚拟方法调度,所以在我看来,它需要某种继承。经典的构造函数没有问题,但根据我刚刚做的这个jsperf,使用构造函数创建对象往往比对象文字慢。有时速度会慢很多,比如本例中的Firefox。因此,我不愿意为每个产生这种开销,例如,复数值的数字中间值,只是为了让我的运算符重载工作。

我在这个jsperf中尝试的另一种方法是不使用原型,而是将虚拟方法存储为每个对象实例的属性。在几乎所有测试过的浏览器上运行速度都很快,但这里我担心对象的大小。我担心对象有两个实际的浮点值,但可能有多达50个不同的成员函数来处理所有成对的运算符重载。

第三种方法是使用单个add函数,该函数以某种方式检查其自变量的类型,然后根据该类型做出决策。可能在由一些数字类型标识符的组合索引的某个列表中查找实际实现。我还没有把它写出来进行测试,但这种类型检查感觉很慢,我也怀疑JIT编译器是否能够优化这种奇特的函数调度。

有没有什么方法可以欺骗当前的JavaScript实现,让它们对创建成本低廉且不占用过多内存的对象进行适当的优化双重调度?

第三种方法看起来很可行:

function Complex(re, im) {
    return {type:'c', re:re, im:im }
}
function Real(n) {
    return {type:'r', n:n }
}
funcs = {
    add_c_r: function(a, b) {
        console.log('add compl to real')
    },
    add_r_c: function(a, b) {
        console.log('add real to compl')
    }
}
function add(a, b) {
    return funcs["add_" + a.type + "_" + b.type](a, b);
}
add(Complex(1, 2), Real(5))
add(Real(5), Complex(1, 2))

一个额外的字段+一个间接寻址是合理的成本。