Saturday, August 20, 2011

Overloading the in operator

In all its "sillyness", the CoffeeShit project gave me a hint about the possibilities of an overloaded in operator.



The Cross Language Ambiguity

In JavaScript, the in operator checks if a property is present where the property is the name rather than its value.


"name" in {name:"WebReflection"}; // true



However, I bet at least once in our JS programming life we have done something like this, expecting a true rather than false.


4 in [3, 4, 5]; // false

// Array [3, 4, 5] has no *index* 4





The Python Way

In Python, as example, last operation is perfectly valid!


"b" in ("a", "b", "c") #True

4 in [3, 4, 5] # True

The behavior of Python in operator is indeed more friendly in certain situations.



value in VS value.in()

What if we pollute the Object.prototype with an in method that is not enumerable and sealed? No for/in loops problems, neither cross browsers issues since if it's possible in our target environment, we do it, otherwise we don't do it ... a fair compromise?





Rules

  • if the target object is an Array, check if value is contained in the Array ( equivalent of -1 < target.indexOf(value) )


  • if the target object is an Object, check if value is contained in one of its properties ( equivalent of for/in { if(object[key] === value) return true; } ).

    I don't think nested objects should be checked as well and right now these are not ( same as native Array#indexOf ... if it's an array of arrays internal arrays values are ignored and that's how it should be for consistency reason )


  • if the target object is a typeof "string", check if value is a subset of the target ( equivalent of -1 < target.indexOf(value) ).

    The Python logic on empty string is preserved since in JavaScript "whateverStringEvenEmpty".indexOf("") is always 0


  • if the target property is a typeof "number", check if value is a divisor of the target ( as example, (3).in(15) === true since 15 can be divided by 3)
I didn't came out with other "sugarish cases" but feel free to propose some.



Compatibility

The "curious fact" is that in ES5 there is no restrictions on an IdentifierName.

This is indeed different from an Identifier, where in latter ReservedWord is not allowed.

"... bla, bla bla ..." ... right, the human friendly version of what I've just said is that obj.in, obj.class, obj.for etc etc are all accepted identifiers names.

Accordingly, to understand if the current browser is ES5 specs compliant we could do something like this:


try {

var ES5 = !!Function("[].try");

} catch(ES5) {

ES5 = !ES5;

}



alert(ES5); // true or false

Back in topic, IE9 and updated Chrome, Firefox, Webkit, or Safari are all compatible with this syntax.



New Possibilities

Do you like Ruby syntax?


// Ruby like instances creation (safe version)

Function.prototype.new = function (

anonymous, // recycled function

instance, // created instance

result // did you know if you

// use "new function"

// and "function" returns

// an object the created

// instance is lost

// and RAM/CPU polluted

// for no reason?

// don't "new" if not necessary!

) {

return function factory() {



// assign prototype

anonymous.prototype = this.prototype;



// create the instance inheriting prototype

instance = new anonymous;



// call the constructor

result = this.apply(instance, arguments);



// if constructor returned an object

return typeof result == "object" ?

// return it or return instance

// if result is null

result || instance

:

// return instance

// in all other cases

instance

;

};

}(function(){});



// example

function Person(name) {

this.name = name;

}



var me = Person.new("WebReflection");

alert([

me instanceof Person, // true

me.name === "WebReflection" // true

].join("\n"));



Details on bad usage of new a part, this is actually how I would use this method to boost up performances.



// Ruby like instances creation (fast version)

Function.prototype.new = function (anonymous, instance) {

return function factory() {

anonymous.prototype = this.prototype;

instance = new anonymous;

this.apply(instance, arguments);

return instance;

};

}(function(){});



cool?



Do Not Pollute Native Prototypes

It does not matter how cool and "how much it makes sense", it is always considered a bad practices to pollute global, native, constructors prototypes.

However, since in ES5 we have new possibilities, I would say that if everybody agrees on some specific case ... why not?

for/in loops can now be safe and some ReservedWord can be the most semantic name to represent a procedure as demonstrated in this post.

Another quick example?


// after Function.prototype.new

// after Person function declaration

Object.defineProperty(Object.prototype, "class", {

enumerable: !1,

configurable: !1,

get: function () {

return this.__proto__.constructor;

}

});



var me = Person.new("WebReflection");



// create instance out of an instance

var you = me.class.new("Developer");



alert([

you instanceof Person, // true

you.name === "Developer" // true

].join("\n"));



I can already see Prototype3000 farmework coming out with all these magic tricks in place :D

Have fun with ES5 ;)

No comments:

Post a Comment