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

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

No comments: