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

Wednesday, August 18, 2010

A Pascal record via JavaScript

... all right, I am off for holidays and I won't have probably time to write the post I have twitted a few days ago since it could take a whole day ... however, I am "cleaning" my good old room here in Italy and I have found an Italian book titled "ALGORITMI IN PASCAL" (Pascal Algorithms) a back in 1987 IT book for secondary school.

I could not resist to read it entirely, just to see what kind of Algo I could have found there ... but "surprise", it was not about algos, more about (T)Pascal itself.

Since at the of the day Pascal is somehow the basis for all we can write today, I thought it would have been a nice experiment to try to reproduce the record concept via JavaScript.

What Is a Record

Nothing different from a predefined structure of types, similar to a classic literal object in JS except the structure defines type hints.

// simple Pascal function example
function personToString(self:person);
begin
writeln(self.name, ' is ', self.age, ' years old')
end;

// basic record example
type person = record
name:string[20];
age:integer
end;

// record usage
var me:person;

begin
me.name := "Andrea";
me.age := 32;
personToString(me)
end.

Please note I don't even know if above program will ever run so take it just as an example.

Record VS Prototype

As we can see the record is not that different from a "class" description via prototype, except rather than values we can specify the type of these values.
This approach may be handy even in JavaScript since there is no type hint but sometimes we may desire to make some behavior consistent and hopefully safer.

A first JavaScript record example

Using some ES5 feature, we could be able to write something like this:

function record(description) {
// (C) WebReflection - Mit Style License
var
shadow = {},
self = {}
;
Object.keys(description).forEach(function (key) {
var
type = description[key],
check
;
if (!type) {
throw new TypeError("unable to check a type for property: " + key);
}
switch (typeof type) {
case "string":
check = function (value) {
return typeof value == type;
};
break;
case "function":
type = type.prototype;
default:
check = function (value) {
return type.isPrototypeOf(value);
};
break;
}
this(self, key, {
get: function () {
return shadow[key];
},
set: function (value) {
if (!check(value)) {
throw new TypeError("type violation for property: " + key);
}
shadow[key] = value;
},
configurable: false
});
}, Object.defineProperty);
return self;
}

Here a basic usage example for above function:

var person = record({
name: "string",
age: "number",
toString: Function
});

person.name = "Andrea";
person.age = 32;
person.toString = function () {
return this.name + " is " + this.age + " years old";
};

alert(person);
// Andrea is 32 years old

Following the same concept we may decide to add arbitrary types and relatives checks such "integer", "real", or whatever we need.
Here there is a version compatible with almost all browsers but IE:

function record(description) {
// (C) WebReflection - Mit Style License
function getter(key) {
return function () {
return shadow[key];
};
}
function setter(key) {
var
type = description[key],
check
;
if (!type) {
throw new TypeError("unable to check a type for property: " + key);
}
switch (typeof type) {
case "string":
check = function (value) {
return typeof value == type;
};
break;
case "function":
type = type.prototype;
default:
check = function (value) {
return type.isPrototypeOf(value);
};
break;
}
return function (value) {
if (!check(value)) {
throw new TypeError("type violation for property: " + key);
}
shadow[key] = value;
};
}
var
shadow = {},
self = {}
;
for (var key in description) {
if (description.hasOwnProperty(key)) {
self.__defineGetter__(key, getter(key));
self.__defineSetter__(key, setter(key));
}
}
return self;
}


Record And Prototype Problem

Unfortunately we cannot use this strategy to create constructors prototypes since the shared object (singleton) will use a single shadow for every instance.

function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype = record({
name: "string",
age: "number",
toString: Function
});

Person.prototype.toString = function () {
return this.name + " is " + this.age + " years old";
};

var me = new Person("Andrea", 32);
var other = new Person("unknown", 29);

alert(me); // unknown is 29 years old

To avoid this problem we should use return this["@" + key]; in the getter and this["@" + key] = value; in the setter, setting if possible the property as enumerable:false if we have an ES5 friendly environment.
This should make the trick but still there is something not that friendly for JS developers.

A Proper JSRecord


A more flexible approach would let us define at the same time both type and default value, so that prototype definition and type hint could be done in one single call.
How could we write something similar? Here my last revision (done while I was writing this post):

function JSRecord(description) {
// (C) WebReflection - Mit Style License
var
self = {},
cDONTwe = {configurable: true, writable: false, enumerable: false},
hasOwnProperty = self.hasOwnProperty,
defineProperty = Object.defineProperty
;
Object.keys(description).forEach(function (key) {
var
current = description[key],
type = hasOwnProperty.call(current, "type") ? current.type : current,
enumerable = hasOwnProperty.call(current, "enumerable") ? current.enumerable : true,
$key = "@" + key,
check
;
if (!type) {
throw new TypeError("unable to check a type for property: " + key);
}
switch (typeof type) {
case "string":
check = function (value) {
return typeof value == type;
};
break;
case "function":
type = type.prototype;
default:
check = function (value) {
return type.isPrototypeOf(value);
};
break;
}
defineProperty(self, key, {
get: function () {
return this[$key];
},
set: function (value) {
if (!check(value)) {
throw new TypeError("type violation for property: " + key);
}
cDONTwe.value = value;
defineProperty(this, $key, cDONTwe);
},
configurable: false,
enumerable: enumerable
});
if (hasOwnProperty.call(current, "value")) {
self[key] = current.value;
}
});
return self;
}

How things changed:

// we can have defaults
function Person(name, age) {
this.name = name;
if (age) this.age = age;
}

// we can define a type hint
// or we can define both hint
// and value plus enumerable

Person.prototype = JSRecord({
name: "string",
age: {
type: "number",
value: 0
},
toString: {
enumerable: false,
type: Function,
value: function () {
return this.name + " is " + this.age + " years old";
}
}
});

// we can test everything now
var me = new Person("Andrea", 32);
var other = new Person("unknown");

alert(me); // Andrea is 32 years old
alert(other); // unknown is 0 years old

alert(me instanceof Person); // true

for (var key in me) alert(key);
// name, age

What do you think? Here my partial list of pros and cons:

Pros

  • easy type hint implementation
  • safer objects, properties and methods
  • usable as record or prototype without shared vars problems


Cons

  • requires ES5 compatible environment
  • getters and setters are slower than direct access due function overload
  • type hint is slower due checks for each set

Monday, June 02, 2008

JavaScript prototype behaviour with PHP

One cool thing of JavaScript prototype model, is that you can change dynamically one or more method updating automatically every instance of that constructor.
Even if classic inheritance and OO Programmers hate this feature, it could be really useful in some case.
Another interesting thing, is that thanks to injected scope, you can share a prototype or use one of its defined methods, with every kind of instance.

PHP is (still) dynamically limited


The concept of injected scope, is absolutely extraneous to PHP developers.
Even with a massive usage of Reflection API, it is not possible to use a method of class A with another class B, even if this method contains common usable tasks for both classes.
At the same time, it is not possible to use a function as property, because it is not recognized as function, if called directly, and it cannot contain a $this referer, thanks to engine limitation.

The light comes from PECL


The official repository for PHP extensions, contains a truly interesting one that is able to modify runtime a class and every derived instance.
This extension is absolutely experimental, but right now usable in some version of PHP 5.
Its name is runkit, that with another one, called classkit, let us use PHP in a "more psychedelic way" :D

The prototype behaviour with PHP


Using a couple of technique, such an SPL interface, and runkit extension, I have been able to write and successfully execute a code like this one:

<?php

// basic class
class Demo {
public static $prototype;
}

// prototype assignment, with optional methods
Demo::$prototype = new prototype(
'Demo',
array(
'set_name' => array('$name', '$this->name = $name;'),
'get_name' => array('return $this->name;')
)
);

// a generic instance
$demo = new Demo();
$demo->set_name('Andrea Giammarchi');
echo $demo->get_name(); // Andrea Giammarchi

// add more prototypes
Demo::$prototype->set_age = array('$age', '$this->age = $age;');
Demo::$prototype->get_info = array('return $this->name." is ".$this->age." years old";');

$demo->set_age(30);
echo '
', $demo->get_info();
// Andrea Giammarchi is 30 years old

?>

In few words, I have been able to define a list of methods, to assign as prototype, to use them, and finally add more, usable with pre defined instance without problems.

Shared method emulations


What is possible to do, at this point, is to share one or more method between two classes, and without problems.
It is even possible to assign directly an entire prototype to another one, using the prototype constructor:

<?php

// same stuff of precedent example

class Constructor {

public static $prototype;

// please note that this constructor
// uses method not defined, yet :)
public function __construct($name, $age){
$this->set_name($name);
$this->set_age($age);
}

}

Constructor::$prototype = new prototype('Constructor', Demo::$prototype);

$me = new Constructor('Andrea', 30);
echo $me->get_info();
// Andrea is 30 years old

?>

Of course, another class could simply initialise its public static prototype, and then add, if necessary, only one method:

<?php

class Name {
public static $prototype;
}
Name::$prototype = new prototype('Name');
Name::$prototype->set_name = Demo::$prototype->set_name;

$me = new Name;
$me->set_name('Andrea Giammarchi');
echo $me->name;
// Andrea Giammarchi

?>


The class prototype and its limit


To successfully test above example codes, I have used my prototype class, that as I said, requires usage of SPL and runkit extension as well.

The shared method emulation, is artificial, because of runkit call that will create a new function, with same arguments and body, for each prototoype. Anyway, the usage is, in my opinion, simple as comfortable and without error possibilities, except for == or === operator that will return in every case false (so the trick is to compare the imploded version of both prototypes, if is the identical string, those are the same prototype)

if(implode('',Demo::$prototype->set_name) === implode('',Name::$prototype->set_name))
doYourStuff();


The main limit of this class is created by runkit extension, that does not let me use every kind of method name, and fails for example if I write setName instead of set_name.

In another PHP version, the 5.3.dev, it lets me use every kind of name, but crash when I try to modify one method, or to remove them using unset(ClassName::$prototype->methodName);

For these reason, you can get this class as example, but you know, right now, that with SPL and some cool PECL extension, PHP limits are truly less evident than ever.

Have fun :)