Friday, August 20, 2010

Object.defineProperty ... but Strict!

In my precedent post entitled A Pascal record via JavaScript I have showed a basic function able to emulate type hints behavior via JavaScript.
Even if that was a proof of concept, I consider other languages simulation of unsupported features an error, first of all because the behavior will rarely be exactly the expected one, secondly because our curent programming language may already have something similar to better learn and use.

A new ES5( direction )

As soon as I have written the Pascal example, I have realized that the good "old" Object.defineProperty, implemented in all major browsers (IE < 9 sucks, you know that ...), has basically the same meaning: define object accessors.

The only missing part, intrinsic in the JavaScript nature, is the property type, where this type could be eventually used for arguments or returns checks when the property is a method.

My Early Attempts

Back in May 2007, my JavaStrict experiment was already trying to do something similar, something implemented after 2 years even in dojo framework as well.
This may tell us that "somebody" would really like to put this kind of checks on daily code, even if performances cannot obviously be the best possible one.

Production VS Deployment

Keeping always in mind that performances matter, the new little monster I am going to show here can be flagged with a boolean, nothing different from:

Object.defineStrictProperty.production = true;

Once in place, the whole logic will switch from "strict type" to "free type" and the used function will be the native Object.defineProperty respecting basically the same behavior.

Object.defineStrictProperty / ies

I have put the code in devpro.it, while I am going to show how it works here.

How to declare a type

Next piece of code is the most basic usage of this method.

var myObject = Object.defineStrictProperty({}, "name", {
type: "string"
});

myObject.name = "it's a me!";

alert(myObject.name);

myObject.name = 123;

// throw new TypeError("wrong type");
//
// console.log =>
// Object
// > expected: "string"
// > received: 123



The meaning of "type"

The type property can contain any of these values:
  • string, where the check will be performed against the typeof value (e.g. "string", "number", "boolean", etc)

  • function, where the check will be performed against the value instanceof type (e.g. Function, Object, Array, Date, etc)

  • object, where the check will be performed against the type.isPrototypeOf(value) (e.g. Array.prototype, others ...)

The only exceptions are null and undefined.

How to declare a returned value

If the property type is "function" or Function there are other extra properties we could define such returns and arguments. Here there is a returns example:

var myObject = Object.defineStrictProperties({}, {
name: {
type: "string",
value: "it's a me!"
},
toString: {
type: "function",
returns: "string",
value: function () {
return this.name;
}
}
});

alert(myObject); // it's a me!

myObject.toString = function () {
return 456;
};

alert(myObject);

// throw new TypeError("wrong return");
//
// console.log =>
// Object
// > expected: ["string"]
// > received: 456


Multiple returns

Since JavaScript is dynamic, there is nothing wrong, logic a part, into different returns. To make it possible we simply need to specify an array with all expected types.

// precedent code with this difference
...
toString: {
type: "function",
returns: ["string", "number"],
value: function () {
return this.name;
}
}

// precedent example won't fail now


How to declare expected arguments

Similar returns concept applied to arguments, so that we can specify a single argument type, a list of arguments type, a list of arguments. Here the difference:

...
setName: {
type: "function",
// checks if a single argument
// with typeof string has been sent
arguments: "string",
// same as above
arguments: ["string"],
// checks if two arguments
// with typeof string and number
// has been sent, order matters!
arguments: ["string", "number"],
// checks if one argument
// with typeof string OR number
// has been sent
arguments: [["string"], ["number"]],
value: function (name) {
this.name = name;
}
}
...

The number of arguments and relative overload per call is arbitrary. As example, we could have a method defined with these arguments without problems.

// generic object for Ajax calls
...
get: {
type: "function",
returns: "string",
arguments: [["string"], ["string", "string", "string"]],
value: function (uri, user, pass) {
this.open("get", uri, false, pass && user, pass);
this.send(null);
return this.responseText;
}
}
...

With a simple logic like that we can "enforce" the program to send one argument or three when, if three, all of them will be checked as strings.

Unlimited arguments

We may decide that the first argument is the only one we would like to check. Well, in this case we can simply create a group of allowed types and ignore the rest.
The arguments logic should be flexible enough due JavaScript nature where overloads can only be simulated via number or tye of arguments checks.

Getters and Setters

The whole logic behind is based on get/set property descriptor, where a nice fallback to __defineGetter__ or __defineSetter__ will be used when Object.defineProperty is missing.
This means that we can describe properties the same way we could do via Object.defineProperty, using enumerable and others as well to make the whole code gracefully degradable when the production flag is set to true.

As Summary

Unless developers won't be interested into this approach, I think this method is basically complete as is and we can use it at own risk.
The whole script stays in about 870 bytes minified and gzipped, not a big deal for what it offers.
In any case, I am looking forward for your feedback!

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