Двойная диспетчеризация для динамически типизированных операторов в JavaScript
Я хочу сформулировать алгебраические выражения таким образом, чтобы можно было заменить основные типы чисел. Если хотите, подумайте о комплексных числах, больших целых числах, матрицах и тому подобном. По этой причине я бы написал либо add(a, b)
или же a.add(b)
вместо a + b
, В статически типизированном языке я бы просто использовал перегрузку функции на основе типов add
реализовать различные альтернативы. Но для JavaScript это не работает, поэтому я ищу альтернативы. Выполненный метод зависит от типа обоих операндов.
Одним из способов, который я придумал, был бы следующий механизм двойной отправки:
Напишите выражение как
a.add(b)
,Реализуйте этот метод для данного типа (например, мой собственный
Complex
тип, или встроенныйNumber
типа) следующим образом:add: function(that) { that.addComplex(this); }
Таким образом, имя метода второго вызова кодирует тип одного из операндов.
Реализуйте специализированные методы для работы со всеми комбинациями Например, установить
Number.prototype.addComplex = function(that) { return newComplex(that.real + this, that.imaginary); }
Давайте предположим, что я знаю все типы, поэтому я могу гарантировать, что все комбинации будут обработаны. Что меня сейчас беспокоит, так это создание этих объектов.
Приведенный выше подход в значительной степени зависит от диспетчеризации виртуальных методов, поэтому, на мой взгляд, он требует некоторого наследования. Нет проблем с классическими функциями конструктора, но в соответствии с этим jsperf, который я только что сделал, создание объекта с использованием функций конструктора имеет тенденцию быть медленнее, чем объектные литералы. Иногда медленнее, чем в большинстве случаев, как в случае с Firefox для этого примера. Поэтому я не хочу брать на себя такие накладные расходы для каждого, например, комплексного числового промежуточного звена, просто чтобы заставить моего оператора работать с перегрузкой.
Другой подход, который я попробовал в этом jsperf, заключался бы не в использовании прототипа, а в хранении виртуального метода как свойства каждого отдельного экземпляра объекта. Работает довольно быстро на практически всех протестированных браузерах, но здесь я беспокоюсь о размере объектов. Меня беспокоит наличие объектов с двумя фактическими значениями с плавающей запятой, но, возможно, целых 50 различных функций-членов просто для обработки всех пар перегрузки операторов.
Третий подход будет иметь один add
функция, которая каким-то образом проверяет типы своих аргументов, а затем принимает решение на основе этого. Возможно поиск фактической реализации в некотором списке, проиндексированном комбинацией некоторых числовых идентификаторов типов. Я еще не написал это для теста, но такая проверка типов выглядит довольно медленно, и я также сомневаюсь, что JIT-компилятор сможет оптимизировать этот экзотический вид диспетчеризации функций.
Есть ли какой-нибудь способ обмануть текущие реализации JavaScript, чтобы сделать правильную оптимизированную двойную диспетчеризацию с объектами, которые дешевы в создании и не занимают слишком много памяти?
1 ответ
Третий подход выглядит вполне жизнеспособным:
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))
Одно дополнительное поле + одно косвенное обращение - разумная цена.