My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label operator. Show all posts
Showing posts with label operator. Show all posts

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

Sunday, June 22, 2008

From the future, a PHP JavaScript like Number class, with late static binding and operator overloading

PHP and operator overloading


Not everyone knows that with PHP, and since 2006, it is possible to implement operator overloading, thanks to a PECL extension.
One common PHP VS other language flame, is about PHP poor OOP expressiveness and power.
Another common request from skilled PHP developers, is the possibility to create a class that is able to manage every situation during code execution.
For example, try to think about a Number class, and try to use every native Magic method to perform a simple task like this one:

$i = new Number(123);
$i += 2;
echo $i; // 125

Above code is simply not implementable with current version of PHP 5.2 or greater, if we do not install php_operator.so or .dll.
Unfortunately, this extension is still experimental, and not documented at all.
That is why I have created a full class, based entirely on this extension, and for this post only, compatible with PHP 5.2 (then, without usage of late static binding).

The JavaScript like Number class


Next code, is a complete example of how we could implement operator overloading, to perform every kind of task operator related:

/** JavaScript Number like class
* @author Andrea Giammarchi
* @site webreflection.blogspot.com
* @license Mit Style
*/
class Number {
protected $__value__;
public function __construct($__value__ = 0){
switch(true){
case intval($__value__) == $__value__:
case floatval($__value__) == $__value__:
$this->__value__ = $__value__;
break;
default:
$this->__value__ = (int)$__value__;
break;
}
}
public function __add($__value__){
return new Number($this->__value__ + Number::__value__($__value__));
}
public function __sub($__value__){
return new Number($this->__value__ - Number::__value__($__value__));
}
public function __mul($__value__){
return new Number($this->__value__ * Number::__value__($__value__));
}
public function __div($__value__){
return new Number($this->__value__ / Number::__value__($__value__));
}
public function __mod($__value__){
return new Number($this->__value__ % Number::__value__($__value__));
}
public function __sl($__value__){
return new Number($this->__value__ << Number::__value__($__value__));
}
public function __sr($__value__){
return new Number($this->__value__ >> Number::__value__($__value__));
}
/* in this class, same behavior of __toString
public function __concat($__value__){
return (string)$this->__value__.$__value__;
}
#*/
public function __bw_or($__value__){
return new Number($this->__value__ | Number::__value__($__value__));
}
public function __bw_and($__value__){
return new Number($this->__value__ & Number::__value__($__value__));
}
public function __bw_xor($__value__){
return new Number($this->__value__ ^ Number::__value__($__value__));
}
/* extension error. Anyway, in this class, it is not implemented - two (new Number) are always different
public function __is_identical($__value__){
return $this === $__value__;
}
public function __is_not_identical($__value__){
return $this !== $__value__;
}
#*/
public function __is_equal($__value__){
return $this->__value__ == Number::__value__($__value__);
}
public function __is_not_equal($__value__){
return $this->__value__ != Number::__value__($__value__);
}
public function __is_smaller($__value__){
return $this->__value__ < Number::__value__($__value__);
}
public function __is_smaller_or_equal($__value__){
return $this->__value__ <= Number::__value__($__value__);
}
/* waiting for the patch
public function __is_greater($__value__){
return $this->__value__ > Number::__value__($__value__);
}
public function __is_greater_or_equal($__value__){
return $this->__value__ >= Number::__value__($__value__);
}
#*/
public function __bw_not(){
return new Number(~$this->__value__);
}
/* undefined behaviour, waiting for documentation
public function __bool(){
return $this->__value__ != 0;
}
public function __bool_not(){
return $this->__value__ == 0;
}
#*/
public function __assign_add($__value__){
$this->__value__ += Number::__value__($__value__);
return $this;
}
public function __assign_sub($__value__){
$this->__value__ -= Number::__value__($__value__);
return $this;
}
public function __assign_mul($__value__){
$this->__value__ *= Number::__value__($__value__);
return $this;
}
public function __assign_div($__value__){
$this->__value__ /= Number::__value__($__value__);
return $this;
}
public function __assign_mod($__value__){
$this->__value__ %= Number::__value__($__value__);
return $this;
}
public function __assign_sl($__value__){
$this->__value__ <<= Number::__value__($__value__);
return $this;
}
public function __assign_sr($__value__){
$this->__value__ >>= Number::__value__($__value__);
return $this;
}
public function __assign_concat($__value__){
throw new Exception('unable to concatenate "'.$__value__.'" with a value of an instanceof '.Number);
}
public function __assign_bw_or($__value__){
return new Number($this->__value__ | Number::__value__($__value__));
}
public function __assign_bw_and($__value__){
return new Number($this->__value__ & Number::__value__($__value__));
}
public function __assign_bw_xor($__value__){
return new Number($this->__value__ ^ Number::__value__($__value__));
}
public function __pre_inc(){
++$this->__value__;
return $this;
}
public function __pre_dec(){
--$this->__value__;
return $this;
}
public function __post_inc(){
return new Number($this->__value__++);
}
public function __post_dec(){
return new Number($this->__value__--);
}
public function __toString(){
return (string)$this->__value__;
}
static protected function __value__($__value__){
return $__value__ instanceof Number ? $__value__->__value__ : $__value__;
}
public function toExponential($decimal = 0){
return sprintf(func_num_args() ? '%.'.$decimal.'e' : '%e', $this->__value__);
}
public function toFixed($decimal = 0){
return (string)(0 < func_num_args() ?
round($this->__value__ * ($decimal = pow(10, $decimal))) / $decimal :
round($this->__value__)
);
}
public function toPrecision($decimal = 0){
if(0 < func_num_args()){
$length = strlen((int)$this->__value__);
if($decimal < $length)
$result = $this->toExponential($decimal);
elseif(strlen($this->__value__) <= $decimal)
$result = str_pad($this->__value__, $decimal, '0', STR_PAD_RIGHT);
else
$result = $this->toFixed($decimal - $length);
}
else
$result = (string)$this->__value__;
return $result;
return sprintf(func_num_args() ? '%.'.$decimal.'e' : '%e', $this->__value__);
}
public function toLocaleString(){
return is_int($this->__value__) ?
(string)$this->__value__ :
number_format($this->__value__, strlen($this->__value__) - strpos($this->__value__, '.') - 1)
;
}
public function toSource(){
return 'new Number('.$this->__value__.')';
}
public function toString(){
return (string)$this->__value__;
}
public function valueOf(){
return $this->__value__;
}
}

With above class we can use type hint checks with functions or method:

function atLeastOne(Number $total){
return 0 < $total;
}

if(atLeastOne(new Number(1)))
echo 'here we go';

We can do almost everything we could do with a primitive int or float value, but we have methods too, while we cannot implement them in a primitive value:

$f = new Number(123);
$f += new Number(0.345);
echo $f->toFixed(2); // 123.35

This case is about numbers, but nothing block us to create classes like, for example, a Date one, where to know the difference between two dates you could simply do:

echo new Date() - new Date($twohoursago); // 7200


The introduction of late static binding


The biggest limit ever, in PHP OOP, is the implementation of self keyword, that does not allow us to extend easily a class.
For example, the Number class, cannot be extended in a reasonable way, because since every method use static public self::__value__ one to get correct information from received value, we should override every method to let them point into different static one.
Indeed, it is natural to think about an Int, and a Float class, but we cannot create them quickly and logically, while using this Number class implementation from the future it is possible to create Int and Float in few lines of code:

class Int extends Number {
static protected $__CLASS__ = __CLASS__;
public function __construct($__value__ = 0){
$this->__value__ = static::__value__($__value__);
}
static protected function __value__($__value__){
return intval($__value__ instanceof Number ? $__value__->__value__ : $__value__);
}
}

class Float extends Number {
static protected $__CLASS__ = __CLASS__;
public function __construct($__value__ = 0){
$this->__value__ = static::__value__($__value__);
}
static protected function __value__($__value__){
return floatval($__value__ instanceof Number ? $__value__->__value__ : $__value__);
}
}


GO, OPERATOR, GO!!!


We have just seen that operator overloading could open a lot of doors for PHP OOP development, and as is for the SPL, the best thing PHP developers have done since PHP 5 release, and as was for the Go, PHP5 Go!!! period, I do like to be able to create classes that are powerful, expressive, and without boring limits for our fantasy, control, and finally to let us be truly proud to develop with such amazing language. Next step? A mix of runkit extension, operator, and SPL, to create a real JavaScript implementation in pure PHP ( how could I call them, Phino? )