Overloading Arithmetic Operators in JavaScript?

JavascriptOperator Overloading

Javascript Problem Overview


This is the best way I can think of phrasing this question, given this JavaScript "class" definition:

var Quota = function(hours, minutes, seconds){
    if (arguments.length === 3) {
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
        
        this.totalMilliseconds = Math.floor((hours * 3600000)) + Math.floor((minutes * 60000)) + Math.floor((seconds * 1000));
    }
    else if (arguments.length === 1) {
        this.totalMilliseconds = hours;
        
        this.hours = Math.floor(this.totalMilliseconds / 3600000);
        this.minutes = Math.floor((this.totalMilliseconds % 3600000) / 60000);
        this.seconds = Math.floor(((this.totalMilliseconds % 3600000) % 60000) / 1000);
    }
    
    this.padL = function(val){
        return (val.toString().length === 1) ? "0" + val : val;
    };
    
    this.toString = function(){
        return this.padL(this.hours) + ":" + this.padL(this.minutes) + ":" + this.padL(this.seconds);
    };
    
    this.valueOf = function(){
        return this.totalMilliseconds;
    };
};

and the following test setup code:

var q1 = new Quota(23, 58, 50);
var q2 = new Quota(0, 1, 0);
var q3 = new Quota(0, 0, 10);

console.log("Quota 01 is " + q1.toString());    // Prints "Quota 01 is 23:58:50"
console.log("Quota 02 is " + q2.toString());    // Prints "Quota 02 is 00:01:00"
console.log("Quota 03 is " + q3.toString());    // Prints "Quota 03 is 00:00:10"

Is there any way of implicitly creating q4 as a Quota object using the addition operator as follows...

var q4 = q1 + q2 + q3;
console.log("Quota 04 is " + q4.toString());    // Prints "Quota 04 is 86400000"

rather than resorting to...

var q4 = new Quota(q1 + q2 + q3);
console.log("Quota 04 is " + q4.toString());    // Prints "Quota 04 is 24:00:00"

If not what are the best practice recommendations in this area for making custom numeric JavaScript objects composable via the arithmetic operators?

Javascript Solutions


Solution 1 - Javascript

As far as I'm aware, Javascript (at least as it exists now) doesn't support operator overloading.

The best I can suggest is a class method for making new quota objects from several others. Here's a quick example of what I mean:

// define an example "class"
var NumClass = function(value){
    this.value = value;
}
NumClass.prototype.toInteger = function(){
    return this.value;
}

// Add a static method that creates a new object from several others
NumClass.createFromObjects = function(){
    var newValue = 0;
    for (var i=0; i<arguments.length; i++){
        newValue += arguments[i].toInteger();
    }
    return new this(newValue)
}

and use it like:

var n1 = new NumClass(1);
var n2 = new NumClass(2);
var n3 = new NumClass(3);

var combined = NumClass.createFromObjects(n1, n2, n3);

Solution 2 - Javascript

Unfortunately no.

For fallbacks, if you arranged the return values, you could use method chaining

var q4 = q1.plus(p2).plus(q3);

Solution 3 - Javascript

Since everyone down voted my other answer I wanted to post proof of concept code which does in fact work as intended.

This has been tested in chrome and IE.

//Operator Overloading

var myClass = function () {

//Privates

var intValue = Number(0),
    stringValue = String('');

//Publics
this.valueOf = function () {
    if (this instanceof myClass) return intValue;
    return stringValue;
}

this.cast = function (type, call) {
    if (!type) return;
    if (!call) return type.bind(this);
    return call.bind(new type(this)).call(this);
}

}

//Derived class
var anotherClass = function () {

//Store the base reference
this.constructor = myClass.apply(this);

var myString = 'Test',
    myInt = 1;

this.valueOf = function () {
    if (this instanceof myClass) return myInt;
    return myString;
}

}


//Tests

var test = new myClass(),
anotherTest = new anotherClass(),
composed = test + anotherTest,
yaComposed = test.cast(Number, function () {
    return this + anotherTest
}),
yaCComposed = anotherTest.cast(Number, function () {
    return this + test;
}),
t = test.cast(anotherClass, function () {
    return this + anotherTest
}),
tt = anotherTest.cast(myClass, function () {
    return this + test;
});

debugger;

If someone would be so kind as to give a technical explanation WHY this is not good enough I would be happy to hear it out!

Solution 4 - Javascript

Second suggestion:

var q4 = Quota.add(q1, q2, q3);

Solution 5 - Javascript

You can implicitly convert to integer or string, your objects.

Objects are only implicitly converted if JavaScript expects a number or a string. In the former case, the conversion takes three steps:

1.- Call valueOf(). If the result is primitive (not an object) then use it and convert it to a number.

2.- Otherwise, call toString(). If the result is primitive, use it and convert it to a number.

3.- Otherwise, throw a TypeError. Example for step 1: > 3 * { valueOf: function () { return 5 } }

If JavaScript converts to string, steps 1 and 2 are swapped: toString() is tried first, valueOf() second.

http://www.2ality.com/2013/04/quirk-implicit-conversion.html

Solution 6 - Javascript

Paper.js does it, for example with point addition (docs):

var point = new Point(5, 10);
var result = point + 20;
console.log(result); // {x: 25, y: 30}

But it does it using its own custom script parser.

Solution 7 - Javascript

I recently came upon this article: http://www.2ality.com/2011/12/fake-operator-overloading.html .

It describes how you can redefine the valueOf method on objects to do something like operator overloading in javascript. It seems like you can only really perform mutator operations on the objects being operated on, so it wouldn't do what you want. Its interesting nonetheless tho.

Solution 8 - Javascript

I made a script that does operator overloading in JavaScript. It wasn't straight forward to make work, so there are a few quirks though. I will cross post the caveats here from the project page, otherwise you can find the link at the bottom:

  • Calculation results must be passed to a new object, so instead of (p1 + p2 + p3) you have to do new point(p1 + p2 + p3), (given your user defined object is named "point").

  • Only +, -, * and / are supported, the fifth arithmetic opperator % is not. Coercion to strings (""+p1) and comparisons (p1 == p2) will not work as expected. New functions should be built for these purposes if needed, like (p1.val == p2.val).

  • Finally the computational resources needed to calculate the answer increases quadratically with the number of terms. Therefore only 6 terms is allowed in one calculation chain per default (although this can be increased). For longer calculation chains than that, split the calculations up, like: new point(new point(p1 + p2 + p3 + p4 + p5 + p6) + new point(p7 + p8 + p9 + p10 + p11 + p12))

The Github page.

Solution 9 - Javascript

In addition to what already have been said: overriding .valueOf() may help to produce quite powerful operator overloading. In proof-of-concept Fingers.js lib you can add event listeners in .NET style:

function hi() { console.log("hi") }
function stackoverflow() { console.log("stackoverflow") }
function bye() { console.log("bye") }

on(yourButton).click += hi + stackoverflow;
on(yourButton).click -= hi - bye;

Core idea is to replace temporarily valueOf when on() is called:

const extendedValueOf = function () {
    if (handlers.length >= 16) {
        throw new Error("Max 16 functions can be added/removed at once using on(..) syntax");
    }

    handlers.push(this); // save current function

    return 1 << ((handlers.length - 1) * 2); // serialize it as a number.
};

Number returned can be then de-serialized back into function using handlers array. What's more it's possible extract bit values from final value (func1 + func2 - func3) so effectively you can understand what functions where added, and what functions were removed.

You can check out source on github and play with demo here.

Complete explanation exists in this article (it's for AS3, tough since it's ecmascript it will work for JS either).

Solution 10 - Javascript

I am not sure why people continue to answer this question with no!

There is absolutely a way which I will outline with a very very small script which your don't have to be John Resig to understand...

Before I do so I will also state that in JavaScript the way your constructor would have worked is by checking for arrays or iterating the 'arguments' literal.

e.g. In my constructor of my 'class' I would iterate the arugments, determine the type of the underlying arugments and process it intelligently.

This means that if you passed an array I would iterate the arugments to find an array and then iterate the array to do further processing depending on the type the element in the array.

E.g. -> new someClass([ instanceA, instanceB, instanceC])

However you guys are seeking a more "C" style approach to operator overloading which can actually be achived contrary to populare belief.

Here is a class which I have created using MooTools which does honor operator overloading. In plain old JavaScript you would just utilize the same toString method only attach it to the prototype of instance directly.

My main reason for displaying this approach is because of the text I continually read which states this functionality is "impossible" to emulate. Nothing is impossible only sufficently difficult and I will display this below...

 //////

debugger;

//Make a counter to prove I am overloading operators
var counter = 0;

//A test class with a overriden operator
var TestClass = new Class({
    Implements: [Options, Events],
    stringValue: 'test',
    intValue: 0,
    initialize: function (options) {
        if (options && options instanceof TestClass) {
            //Copy or compose
            this.intValue += options.intValue;
            this.stringValue += options.stringValue;
        } else {
            this.intValue = counter++;
        }
    },
    toString: function () {
        debugger;
        //Make a reference to myself
        var self = this;
        //Determine the logic which will handle overloads for like instances
        if (self instanceof TestClass) return self.intValue;
        //If this is not a like instance or we do not want to overload return the string value or a default.
        return self.stringValue;
    }
});

//Export the class
window.TestClass = TestClass;

//make an instance
var myTest = new TestClass();

//make another instance
var other = new TestClass();

//Make a value which is composed of the two utilizing the operator overload
var composed = myTest + other;

//Make a value which is composed of a string and a single value
var stringTest = '' + myTest;

//////

The most recent display of this nomenclature was observed at XDate's documentation page: http://arshaw.com/xdate/

In this case I believe it was actually even easer, he could have used the prototype of the Date object to achive the same.

None the less the method I have given as an example which should portray this style of utilization for others.

Edit:

I have a complete implementation here:

http://netjs.codeplex.com/

Along with other goodies.

Solution 11 - Javascript

For some limited use cases you can have operator "overloading" effects:

function MyIntyClass() {
    this.valueOf = function() { return Math.random(); }
}
var a = new MyIntyClass();
var b = new MyIntyClass();
a < b
false

a + b
0.6169137847609818

[a, b].sort() // O(n^2) ?
[myClass, myClass]

function MyStringyClass() {
    this.valueOf = function() { return 'abcdefg'[Math.floor(Math.random()*7)]; }
}
c = new MyStringyClass();
'Hello, ' + c + '!'
Hello, f!

The above code is free to use under the MIT license. YMMV.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPeter McGView Question on Stackoverflow
Solution 1 - JavascriptDanView Answer on Stackoverflow
Solution 2 - JavascriptJustin LoveView Answer on Stackoverflow
Solution 3 - JavascriptJayView Answer on Stackoverflow
Solution 4 - JavascriptJustin LoveView Answer on Stackoverflow
Solution 5 - JavascriptLuis NavesView Answer on Stackoverflow
Solution 6 - JavascriptIzhakiView Answer on Stackoverflow
Solution 7 - JavascriptB TView Answer on Stackoverflow
Solution 8 - JavascriptBasic BlockView Answer on Stackoverflow
Solution 9 - Javascriptaverage JoeView Answer on Stackoverflow
Solution 10 - JavascriptJayView Answer on Stackoverflow
Solution 11 - JavascriptJason BoydView Answer on Stackoverflow