Permalink

Courtesy Implementation in Nice

17 AUG 2004

Martin Fowler is talking about "courtesy implementations", which of course is all very sensible. Working with a language with first-class multi-methods makes this even more painless. Here's Fowler's example in Nice:


interface Node {}



class Box implements Node {

	List children = new ArrayList();

	void `<<` (Node argNode) { children.add(argNode); }	

}



class Elephant implements Node {}



int num_elephants(Node n);



num_elephants(Elephant e) = 1;

num_elephants(Box b) {

	var result = 0;

	for (node: b.children) {

		result += node.num_elephants();

	}

	return result;

}



Things to notice about this code:

  • Despite being code for a statically typed language, it's about the same size as Martin's Ruby code, maybe even a little shorter.
  • If you create a new class which implements Node, the compiler will complain if you don't also provide a new implementation of num_elephants to go with it, just like an abstract method in Java.
  • Nice syntax accepts method definitions and overrides within class bodies, but they're still multi-methods, just as if they were defined outside the class. Whatever you're more comfortable with is fine.
  • Because classes don't contain methods, "adding a method to a base class" is as trivial as defining any other kind of method. You want to define new methods on Object, or String? Go right ahead, nothing's stopping you.
  • Everybody brings up the "but what if I have a list of arbitrary objects" question for statically typed languages, so I'll also mention this alternative definition of num_elephants:
    
    	int num_elephants(Object o) = 0;
    
    	num_elephants(Elephant e) = // Same as original
    
    	num_elephants(Box b) = //Same as original
    
    	
    This would let you find the number of elephants in anything. Things that you haven't specialized the method for simply get a default of 0. So, "foo".num_elephants() would return 0. Things that care, can give better results. Things that don't, don't break.
  • Nice has some great power tools from the world of functional programming. Here's a shorter way of implementing num_elephants for the Box class:
    
    	num_elephants(Box b) = b.children.map(num_elephants).foldLeft(
    
    		(int i, int j) => i + j);
    
    	
  • I didn't have to declare num_elephants when I declared Node, I did it later, and that's fine. I could have done it in another file or package, too.

Nice takes this even further, though. You can override a method for a particular object. Smalltalk, Ruby, Python all do this, too. But your garden variety statically typed languages don't let you play this game. Let's go ahead and allow strings to count as elephants, too:


	num_elephants(String s) = s.indexOf("Elephant") > -1 ? 1 : 0

but let's make a special exception:

	num_elephants("SpecialElephant") = 5;

Methods are so convenient, Nice doesn't even have (or need) a switch statement. Think about it.