Archive for May, 2008

Duck Punching JavaScript - Metaprogramming with Prototype

First, let me explain the title for those of you who live in Java world. The term Duck Punch comes from Ruby programmers and refers to the concept of duck typing used by Ruby and other languages; if it walks like a duck and quacks like a duck, I would call it a duck. With that being said, duck punching is summed up nicely by Patrick Ewing with the following:

Well, I was just totally sold by Adam, the idea being that if it walks like a duck and talks like a duck, it’s a duck, right? So if this duck is not giving you the noise that you want, you’ve got to just punch that duck until it returns what you expect.

It is slightly violent, but makes for a good article title. Now, getting to the point, metaprogramming is writing programs that write or manipulate programs, including themselves, at runtime. This type of programming can be accomplished in many languages but is currently a pretty hot topic in languages such as Ruby and Groovy. Java has a similar concept known as Aspect Oriented Programming (AOP), which introduces pointcuts to a program that can then be modified at runtime via bytecode manipulation or dynamic method proxies.

My example library/method below allows a Prototype-enabled class to have every one of its methods wrapped with a before and/or after method to introduce additional functionality at runtime. This concept is demonstrated by this example, note that the modification occurs on the class level and any existing or future instances of that class are affected. Thanks to Prototype JS, this code is pretty simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var Aspect = Class.create();
Aspect.prototype = {
	initialize: function( clazz, before, after ) {
		var members = Object.keys( clazz.prototype );
		members.each(
			function( name ) {
				if( Object.isFunction( clazz.prototype[name] ) ) {
					var oldName = "old_" + name;
					clazz.prototype[oldName] = clazz.prototype[name];
					clazz.prototype[name] = function() {
						if ( before ) before( name );
						var me = this;
						var args = $A( arguments );
						args.each( 
							function( arg ) {
								me[oldName] = me[oldName].curry( arg );
							}
						);
						me[oldName]();
						if( after ) after( name );
					}
				}
			}
		);
	}
}

This code relies on one of the core concepts of JavaScript OOP; a JavaScript object is nothing more than an associative array. Knowing this, when passed a class, we can get a list of all class member names using the Prototype Object.keys method as shown on line 4. Once we have the member names, we loop through all members (line 5) of the class. For each member we check if it is a function (line 7). If it is, we alias the function (line 9) and create a new function (line 10) in its place that wraps the aliased function with calls to the before (line 11) and after (line 20) functions we pass in. Notice the use of clazz.prototype starting on line 9, the prototype keyword is necessary to modify the class. If you rewrote this method slightly and didn’t specify the prototype keyword you could modify an individual instance of the class instead of all running instances. Also notice the curry method on line 16, currying is a metaprogramming technique (provided as a helper by Prototype JS) where a function is modified with each call so that the arguments passed into it become part of the function and are no longer necessary. This allows us to pass an arbitrary number of arguments into the aliased method. (Remember, in JavaScript, arguments do not need to be defined for a function and can be accessed via the arguments array.)

The above code can be called on any class in the document by running the following code, assuming before and after are pre-existing functions and SampleClass is the desired class:

1
new Aspect( SampleClass, before, after );

You can see a complete (although pretty useless) example of this method here, or view the source code for the test or the sample class.

So, where is this useful? Since we’re simulating AOP, we can use this for any cross-cutting concern, a perfect example being logging or profiling. Watch for a future article where I use this technique to automatically implement JavaScript profiling similar to what I did in my article on the dollar function using this technique. One final note for the Prototype geniuses out there, Prototype does provide a wrap method that provides this functionality, but if you used that you never would have learned all the neat things about JavaScript in this example!

Reboot Complete

For those of you visiting to check out my changes for the May 1st Reboot, I’d like to welcome you and encourage you to subscribe to my RSS feed. Sadly, I did not get my design totally finished thanks in part to life in general. If you’ve seen my design before the reboot I think this is a definite improvement; however there are plenty of tiny details that still need a lot of work.

Since I’m away for most of next week at the JavaOne conference in San Francisco I’m planning to do a bit of work on this site while in the air. So, with any luck it should be prettier than it currently is sometime next weekend. If you have any comments, concerns, or suggestions, please feel free to leave a comment on this post. This is a design in progress and I welcome any and all criticism.

Regular updates should now resume since I don’t need the reboot splash screen any longer, so check back tomorrow for a brand new article.