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
end

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

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.

6 comments:

  1. janfri5:30 AM

    Maybe I'm missing something, but it is possible without the extra block.

    See here.

    ReplyDelete
  2. janfri6:05 AM

    Ok, I've got it now. You need the block for the lazy evaluation of the assert_equal parameters. So I'm wrong with my comment above.

    ReplyDelete
  3. The LISP macro syntax is very powerful, but the macros themselves tend in the direction of write-only code; anybody that's tried to figure out what's going on in a heavily macro-driven environment will realize that. (See, for example, some of the Nyquist source [http://www.cs.cmu.edu/~rbd/doc/nyquist/title.html])

    If it absolutely *had* to work in Ruby, I imagine one could do some sort of evil in ParseTree or equivalent; look at the recent posts about ordered hashes in 1.8.

    ReplyDelete
  4. I think you are mixing up power with expressiveness. I'd say that the extra syntax in Ruby is entirely justified since what's happening in that Lisp code seems totally unexpected.

    Further, I'd add that in Ruby you could go one further and overwrite assert_equal itself to accept a block. In that case the only real difference between the Lisp and the Ruby implementations is that in Ruby there is an indication that something unusual may be happening. That indication is part of what divides a powerful expressive language from one that is merely powerful.

    ReplyDelete
  5. http://groovy.codehaus.org/Local+AST+Transformations

    ReplyDelete
  6. Pangloss, I don't see how not being see able to do something, would be more powerful than being able.

    I do understand your worry on the clojure code doing something unexpected, something that writer of the code did not express to you. But I think that language should provide as good tools as possible for that. Clojure macros are truly powerful tool for that.

    ReplyDelete

Note: Only a member of this blog may post a comment.