2

我想以可以交换基础数字类型的方式制定代数表达式。如果你愿意,可以考虑复数、大整数、矩阵等。出于这个原因,我会写要么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 实现对创建成本低且不占用过多内存的对象进行适当优化的双重分派?

4

1 回答 1

2

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

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))

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

于 2013-11-28T13:45:10.163 回答