Overloading Arithmetic Operators in JavaScript?
Asked Answered
A

11

77

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?

Adeliaadelice answered 27/10, 2009 at 23:38 Comment(3)
Have a look at SweetJS which may do exactly that.Squirmy
Here: https://mcmap.net/q/109942/-override-the-equivalence-comparison-in-javascript/632951Meletius
Related: #19621167Psychro
M
39

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);
Mikelmikell answered 28/10, 2009 at 1:22 Comment(2)
2ality.com/2011/12/fake-operator-overloading.html is better.Meletius
I am somewhat miffed that a language that returns negative values from negative operands to its modulus operator would not support operator overloading. Everyone in the world at this point must be implementing % as ((a%b)+b)%bHola
C
24

Unfortunately no.

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

var q4 = q1.plus(p2).plus(q3);
Calandracalandria answered 28/10, 2009 at 1:19 Comment(3)
If your environment supports it, you could also use currying for a prettier API: one(two)(three)Naoise
@elliottcable Good+clever thinking, but while that might be okay for multiplication, even then I don't think it communicates well within the typical programmer mindset. I'd still go with one.times(two).times(three);.Newspaperman
In CoffeeScript, you could drop some of the the parens too :)Lots
Q
16

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!

Quid answered 9/5, 2012 at 0:59 Comment(3)
Passing this to new type in cast can be overidden if required by the derived ....Quid
Jay, can you show how this would work with a MyNumber class to do arithmetic (make an example)?Redshank
Does it work just because the type holds single int value?Millimicron
S
8

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

Sigismundo answered 23/4, 2015 at 2:39 Comment(0)
B
8

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.

Bokbokhara answered 15/9, 2016 at 14:29 Comment(4)
Can you please explain the trick with a small sample code. Thanks. I've seen exemple code.Intrude
the +20 add 20 to the member x and y. line 2Intrude
This snippet is interpreted using the custom parser provided - this is not standard javascript.Bokbokhara
I have not seen the call to this parser in Paper.js exemple. Is it like an very intelligent eval ?Intrude
C
7

Second suggestion:

var q4 = Quota.add(q1, q2, q3);
Calandracalandria answered 28/10, 2009 at 1:20 Comment(0)
M
6

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.

Medico answered 29/1, 2012 at 20:34 Comment(0)
A
5

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.

Always answered 18/11, 2016 at 16:41 Comment(0)
E
3

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

Everywhere answered 28/7, 2014 at 11:19 Comment(0)
Q
2

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.

Quid answered 26/10, 2011 at 14:44 Comment(3)
What have you shown here is not a operator overloading. Moreover in your example toString() returns '0' to stringTest. You should use valueOf() for returning a number substitute (more of it here: adequatelygood.com/2010/3/… ). But that is only a value substitute, this is not operator overloading functionality. Even with your method I cannot implement class Vector which would subtract fields .x and .y when I do like this: vectorC = vectorA - vectorB. For doing like this you require operator overloading, not possible in ES5.Marlinmarline
I have found that using a combination of the above tactics with function.bind that you are able to control which version of the method is called and it does work although it is not as flexible as in other languages... E.g. You can make a .cast function which takes a Object and calls another type's method with the context set to the Object given.... None the less this is not the same as in other languages but I still argue that it can work :PQuid
@Quid I tried this in my browser and in Node.js, and it complains that Class is not defined, so it doesn't work......... Oh wait, I see: Class is from MooTools. Can you make this work with jQuery? Or even better, without any library at all, just JavaScript?Redshank
A
-2

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.

Apery answered 19/3, 2014 at 16:50 Comment(3)
Free to use under the MIT license? I don't think you understand what this site is all about.Beall
@AranMulholland Do you? Current SE license is CC BY-SA (has always been), and they are planning to move to some kind of MIT meta.stackexchange.com/questions/272956/…Ankylostomiasis
If I understand correctly, this does not overload the operator, but simply delegates to its original implementation. So very limited use as you point out.Squirmy

© 2022 - 2024 — McMap. All rights reserved.