Saturday, January 05, 2008

Reflection on Everything

Smalltalk allows you to reflect on everything. You can not reflect on objects to find their instance variables and methods. But, you can also reflect on code running and on the stack. It's powerful stuff. Why would you need this extra power? You might be a lazy developer. You might constantly remind yourself, "I work in a dynamic language and it should work for me!" Let's go digging shall we?

One thing you have to be careful in dynamic languages are message not understood or method missing errors. One easy mistake to make is to do the following:

What's wrong with the above code? Well, are you trying to catch if doSomething is not understood by someObject? If so, the call can still succeed and there could be a nasty bug further down in the code. The handler will be giving misinformation on the true problem. Frustrating to say the least. It's better to do something like this:

Yuck. But, it does check the receiver and selector to make sure we captured the right exception. Lots of typing for something simple. Granted you shouldn't be doing a lot of guarding against MessageNotUnderstoods (polymorphism anyone?). But, sometimes it is necessary. Besides, we wouldn't have this fun little blog post would we. Basically, the above method checks for the right selector and receiver that we expected to have a MessageNotUnderstood and if it did we do our logging code. If not, it's something we didn't account for and is a bug, thus we "pass" the exception to the next handler. But, how can we prevent ourselves from all of this typing?

Squeak has this method implemented:

BlockConext>>onDNU: selector do: handleBlock "Catch MessageNotUnderstood exceptions but only those of the given selector (DNU stands for doesNotUnderstand:)"

It looks like what we started with, but this version is safe. It will pass the MessageNotUnderstood exception if the send did not happen directly from the block we defined.

What is the method above doing? The method I wrote reflects on the stack and the compiled code. I use the stack to find the receiver that should be in the block. The compiled code is needed to find all of the selectors that are called directly from the block. Pretty cool, huh?

Smalltalk is one of the few languages where you can reflect on everything on the stack. Stack frames are objects too. It makes doing difficult things possible.