Monday, August 17, 2009

Macros Facilitate Expressive Code

Someone once asked me if I thought Clojure was more expressive than even Ruby. I didn't have enough information to form an opinion then, and I still don't now. However, I recently noticed something that led me to believe the answer could actually be yes.

I was looking through the code of clojure.test on Friday and I noticed something interesting. In clojure.test, the form(s) passed to the "is" macro are wrapped by a try/catch. It caught my eye because I often want to do the same thing in other languages, and usually I have to settle for much less elegant solutions.

Here's a bit of example code to help create some context
(deftest a-test
(is (= 1 (some-function-that-throws-an-exception)))
(println "this code still executes"))
For this example to work you'll have to ignore the fact that you probably don't want this behavior. In practice I prefer my tests to abort on the first failing assertion; however, in this blog entry I'm focusing on what's happening, not what I theoretically prefer.

In the example I call a function the same way I would anywhere else, and the framework has full control over what happens if my function throws an exception. This is accomplished when the "is" macro takes the forms and manipulates them into something similar to the original code, but with additional capabilities.

This particular example struck me as one where macros allow you to write only what you want, and the macro adds the additional behavior that you desire. However, the key is, you don't have to do anything special to get this additional behavior.

Consider trying to do the same thing in Ruby. In Test::Unit you would need an assert method that took a block.

def safe_assert_equal
result = yield
assert_equal result.first, result.last
rescue Exception=>e
puts e

class Testable < Test::Unit::TestCase
def test_something
safe_assert_equal { [1, 2] }

The additional syntax isn't drastically invasive; however, you are forcing additional syntax and requiring the understanding of why it's necessary.

Of course, in Java things would be even less expressive. The most likely solution would be to put the assertEquals in a Runnable, but I'd be interested in hearing other ideas. Regardless of the solution, it would obviously be invasive and take away from the readability of the test.

Being able to only say what is necessary is powerful. Stuart Halloway likes to talk about Essence and Ceremony, where Essence is the ability to say only what you want, and Ceremony is all the additional things you are required to say.

Macros seem to be a powerful tool for those looking to write Ceremony-free code.
Post a Comment