Skip to main content

More Literate Programming: Language-Level Anaphora

Posted by tomwhite on June 29, 2006 at 1:21 PM PDT

Last month I blogged about Literate Programming with jMock, and also about using anaphora to avoid repetition in the tests. (An anaphor is a word like it that refers to something previously referred to.)

This got me thinking: is it possible to use anaphora more widely at the language level? Would such constructs be useful? Before trying to do this in Java I looked at more dynamic languages, starting with a very quick look at Lisp, where I first came across anaphora in programming languages.

Common Lisp

Paul Graham, in his wonderful book ANSI Common Lisp (which is well worth a read, by the way, even if you never intend to write a line of Lisp), introduces an anaphoric if that captures the test condition in a variable called it. His example is:

(aif (calculate-something)
     (1+ it)
     0)

This can be read as "if the variable calculate-something is non-zero, then return one plus its value, otherwise return zero". The aif construct is actually a macro, a very powerful feature that effectively allows you to re-write the language. I won't go into the details here as it's very clearly explained in the book. In a nutshell, the Lisp compiler transforms the expression into another chunk of code (defined by the macro) that it can understand - in this case a regular if with a bit of variable capture.

Let's try another language - Ruby.

Ruby

Here's how aif would be used in Ruby:

hash = { 'a' => 'peach', 'b' => 'pear', 'c' => 'plum'}
aif hash['a'] do
  puts @it
end

This code snippet prints peach to the console

Ruby doesn't have macros, so the way this is implemented is not like Lisp, it's actually closer to the jMock approach. We basically extend Object, and add an aif method that takes a conditional expression and a block. If the expression is true, then the instance variable @it (instance variables always start with @ in Ruby) is set to the value of the expression and the block is executed. Here's the definition:

class Object
  def aif(expression)
    if expression
      @it = expression
      yield
    end
  end
end

The Ruby implementation is inferior to the Lisp one in a few ways (although some of this could be down to my inexperience in Ruby - I invite seasoned Rubyists to improve the code!). Firstly, there is no way that I can see of supporting an else clause for aif (the Lisp version does this easily). Secondly, there are concurrency issues - storing state in an instance variable is dangerous if multiple threads are using the object. However, this is fairly straightforward to solve with synchronization or by using thread-local variables. Thirdly, there is a minor, but potentially irritating, syntactic difference between aif and if: aif takes a block and hence has an extra do. Compare:

if hash['a']
  puts hash['a']
end

and

aif hash['a'] do
  puts @it
end

Java

Can you do the same thing in Java? Let's try to replicate the Ruby approach since Java doesn't have Lisp macros. We can't add methods to Object in the way we can in Ruby, so instead create a class called AnaphoricObject with a static method called aif. We can then either extend AnaphoricObject or statically import aif when we want to use anaphoric if.

Now we hit a problem: what does the definition of aif look like? It would take an Object as the test expression, but then we would need to cast the it variable to the type we wanted. Doable, but not very pleasant. (Aren't we trying to improve readability?) Worse, how do we supply a block of code? We can't do it. There is probably a way to do it using anonymous inner classes, but I don't want to go down that alley - we'll lose all improvements in syntax which is why we were trying to do this in the first place.

Conclusion

Obviously, I like Java a lot, but I know its limits. Anaphora work well for an API like jMock, but not at the Java language level. Even Ruby, touted for its ease of meta-programming, struggles to provide a nice implementation of anaphoric if (although, again, I'd be happy to be proved wrong on this). Lisp manages it, if only because it has macro support. (It's probably possible in your favourite language too.)

But, do we really need anaphoric if - after all, you can probably argue that you've never needed it. This is actually a weak argument. For example, I use Java 5 static imports all the time now and wouldn't like to give them up, but I didn't rage about not having them before I was introduced to them. I didn't know what I was missing. Similarly, once you've used constructs like anaphoric if, you get to know when it is useful, then start missing it in languages where it's not available. Here's Paul Graham again (from ANSI Common Lisp):

Is it worth writing a macro just to save typing? Very much so. Saving typing is what programming languages are all about; the purpose of the compiler is to save you from typing your programs in machine language. And macros allow you to bring to your specific applications the same kinds of advantages that high-level languages bring to programming in general. By the careful use of macros, you may be able to make your programs significantly shorter than they would be otherwise, and proportionately easier to read, write, and maintain.

Although he's talking about macros, I think the point is more general: it's worth striving for higher-level abstractions to make our programs more literate.

Related Topics >>

Comments

"which is well worth a read,

"which is well worth a read, by the way, even if you never intend to write a line of Lisp" I regularly hear this said about Lisp books. I've never heard this said about a PHP book, or a Visual Basic book, or even a C++ book.