The Source for Java Technology Collaboration
User: Password:



Tom White

Tom White's Blog

More Literate Programming: Language-Level Anaphora

Posted by tomwhite on June 29, 2006 at 01:21 PM | Comments (5)

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.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Interesting.However, from a readability aspect there are always two issues I keep coming back to with these kinds of dynamic constructs, ever since pre-compiler macros in C:
    The first time I come across a new "word" like aif, how do I find out what it means?
    The second time I across the word (seemingly the same), how do I know it really is the same and not subtly but vitally different?

    So while this kind of thing is wonderful in your own code, it could be a nightmare when you see it in someone else's code. Which brings the opposing forces that dynamic constructs give power, but strict standard apis give a different kind of power. When we solve the opposition we can gain real power.

    Posted by: tobega on June 29, 2006 at 11:17 PM

  • This kind of dynamic stuff is a real threat to readability and maintainability. I once looked at a popular JavaScript-based HTML editor. The declarations of all central objects are essentially - empty! All methods are added some time later, depending on the browser environment. You essentially have to know the whole code to get a clue which methods are available (and when they *become* available)...

    The compiler of strongly typed language is my best friend in projects with some thousands line of code.

    Posted by: scotty69 on June 30, 2006 at 06:06 AM

  • What about this for your first example?

    int calculate-something = 3;
    int result = calculate-something != 0 ? 1 + calculate-something : 0;

    Posted by: carmello on July 01, 2006 at 03:02 PM

  • Re: the Ruby aif.
    One of the purposes of aif is to avoid recomputing the test expression. Your ruby aif fails in this regard, because it evaluates the expression twice, once in the test and once in the assignment (unless there's some crazy behiind the scenes caching involved; i'm not a rubyist) Controlling evaluation is the reason macros are powerful.

    Re: how do I find out what it means?
    You read the documentation string cleverly provided by your environment as soon as you type 'aif'. If that's not sufficient, you hit alt-. and read the source (about 4 lines in this case) or macroexpand the expression.

    Re: The second time I see it, how do I know it really is the same?
    The second time you see a function or method name, how do you know its the same? No difference here.

    Posted by: codyk on July 31, 2006 at 12:23 PM

  • A Java compatible option may be to use Groovy. Scripting for functional/acceptance tests may be a general advantage too.

    Groovy closures should allow you to pass in the code block as you do with Ruby. As for adding methods to Object, I must confess I don't know.

    Ivan

    Posted by: ivanjensen on November 05, 2006 at 02:29 PM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds