topics: functional programming, concurrency, web-development, REST, dynamic languages

Sunday, June 15, 2008

fun with with - part II

Here is one implementation of a function 'scope' which lets us write code like:

with (scope({ sn : dk.ourclient.supernavigator,
u : com.trifork.utils})) {


with(sn) include()(model,view)
with(u) include()(Format)

run(function(mod,view,fm){
//do stuff
});
}

The function that is given to 'run' will be called with variable 'mod' bound to dk.ourclient.supernavigator.model, 'view' bound to dk.ourclient.supernavigator.view and with 'fm' bound to com.trifork.utils.Format.

Note that it is important that the function given to run is executed in a scope that does not see the functions introduced by the outer 'with' statement - this would be dangerous. In order t acheive this, the variable 'run' 'include' and 'sn' and 'u' are written so that they can only be used one time: once called they delete the reference to them selves. Similarly, suppose com.trifork.utils has a property 'include' then if we did:

with(u) include()(Format,include)
we would expect the 'include' from u, and not the include function from our "package DSL".

This code does that (though I haven't tested it thoroughly):

function scope(spec){
if (spec.hasOwnProperty('module')) {
throw new Error('Reserved word "module" may not be used.');
}
if (spec.hasOwnProperty('run')) {
throw new Error('Reserved word "run" may not be used.');
}

var objects = [];

for (var p in spec) if (spec.hasOwnProperty(p)) {
spec[p] = object(spec[p])
spec[p].include = function(){
delete this.include;
return function(){
for (var i=0,N=arguments.length;i<N;i++){
objects.push(arguments[i]);
}
};
};
}
spec.run = function(fn){
for(var p in this) {if (this.hasOwnProperty(p)) {
delete this[p];
}}
return fn.apply(objects[0],objects);
}
return spec;
}

Thursday, June 12, 2008

fun with with


WARNING: After reading this blog you may start playing around with the evil 'with' statement in JavaScript. It's actually quite fun, but note that I'm not encouraging it's use in general.

In a previous blog I advocated the use of namespacing in JavaScript programs. To support namespacing I presented a pair of functions 'namespace' and 'using'. The former introduces a namespace, e.g.,

namespace({
com:
trifork:
tribook: ['model','view','controller']});

ensures that object 'com' exists with a property 'trifork' which in turn has a property 'tribook', etc. The using function brings such a 'package' or 'module' object into scope. For example

using(com.trifork.tribook.model,
com.trifork.tribook.view).run(function(model,view){

var meet = new model.Room('meeting 1');

});

Now, in the last couple of months I have been working in several projects using these functions, and I feel they have really helped me structure the JavaScript code -- one project has several thousands of lines of JS code structured in files and modules mirroring the package structure (i.e., dk.ourclient. ...). In these projects, a usage pattern occurs. Many files started:

namespace('dk.ourclient.supernavigator.controller');

using(dk.ourclient.supernavigator.view,
dk.ourclient.supernavigator.model,
dk.ourclient.supernavigator.controller).run(function(v,m,c) {

c.ContactController = function(spec) {
//... m.xxx, v.yy
};

});

I was annoyed with the WET (i.e. as opposed to DRY) repeated occurrence of 'dk.ourclient.supernavigator'. One alternative pattern would be:

namespace('dk.ourclient.supernavigator.controller');

using(dk.ourclient.supernavigator).run(function(sn) {
var m = sn.model,
c = sn.controller,
v = sn.view;

c.ContactController = function(spec) {
//... m.xxx, v.yy
};

});

Which is what we started using. However I was looking at an alternative way to bring sub-modules or packages into scope. I experimented with several forms; here is one of them.


You can think of the following as a small DSL for importing or using package objects in javascript (note, part of the DSL is the JavaScript with statement). It uses a form: (where 'obj' is a package object to be used)

with(f(obj)) {
//code
}

By introducing an intermediate function 'f' and using 'f(obj)' instead of 'obj' itself, we can avoid some of the issues with 'with'.

Question: Does the following code make sense: (with an appropriate definition of the 'scope' function, this is valid working JS code!)

with (scope({ sn : dk.ourclient.supernavigator,
u : com.trifork.utils})) {


with(sn) include()(model,view)
with(u) include()(Format)

run(function(mod,view,fm){
//do stuff
});
}

What does it do? Wait and think before you read on...

I will post an update with code that makes this work later. Hope this is good fun to you, but note that in our project we used the simpler form mentioned previously:

using(dk.ourclient.supernavigator).run(function(sn) {
var m = sn.model,
c = sn.controller,
v = sn.view;

c.ContactController = function(spec) {
//... m.xxx, v.yy
};

});

and I'm sure that Crockford would prefer this too ;-).

/Karl