Monday, September 10, 2007

Ruby Matters: "Design Patterns" in Dynamic Languages

As I mentioned in my last post (Ruby Matters: Meta-programming, Synthesis, and Generation), the Gang of Four design patterns book should have been named "Palliatives for C++". One of the authors finally admitted as much in public at a roast. So, why would design patterns be any different in a dynamic language (like Smalltalk or Ruby)?

In the GoF book, design patterns are 2 things: nomenclature and recipes. The nomenclature part is useful. It's a way of cutting down on repetition of similar code across projects, and it gives developers a way to talk to one another, using shorthand. Instead of saying "I need to create an object that can only be instantiated once", you say "singleton" (we'll leave aside for the moment why Singleton is evil -- the subject of another blog entry). Nomenclature good.

But the fatal flaw in the GoF book was that they included recipes. And many people thought they were the best part. Even now, you see books on Java design patterns that blindly mimic the structure of the examples in the GoF book (even though Java has some better mechanisms, like interfaces vs. pure virtual classes). Recipes bad. Because they suggest more than just a way to name common things. They imply (and put in you face) implementation details.

Because of meta-programming, many of the design patterns in the GoF book (especially the structural ones) have much simpler, cleaner implementations. Yet if you come from a weaker language, your first impulse is to implement solutions just as you would from the recipe.

Here's an example. Let's say you have a simple Employee class, with a few fields (which could be properties in the Java sense, but it makes no difference for this example).


public class Employee {
public String name;
public int hireYear;
public double salary;

public Employee(String name, int hireYear, double salary) {
this.name = name;
this.hireYear = hireYear;
this.salary = salary;
}

public String getName() { return this.name; }
public int getHireYear() { return this.hireYear; }
public double getSalary() { return this.salary; }
}


Now, you need to be able to sort employees by any one of their fields. This is a flavor of the Strategy Design Pattern: extracting an algorithm into separate classes so that you can have different flavors. Java already includes the basic mechanism for this, the Comparator interface. So, here's what the code to be able to compare on any field looks like in Java:


public class EmployeeSorter {
private String _selectionCriteria;

public EmployeeSorter(String selectionCriteria) {
_selectionCriteria = selectionCriteria;
}

public void sort(List<Employee> employees) {
Collections.sort(employees, getComparatorFor(_selectionCriteria));
}

public Comparator<Employee> getComparatorFor(String field) {
if (field.equals("name"))
return new Comparator<Employee>() {
public int compare(Employee p1, Employee p2) {
return p1.name.compareTo(p2.name);
}
};
else if (field.equals("hireYear"))
return new Comparator<Employee>() {
public int compare(Employee p1, Employee p2) {
return p1.hireYear - p2.hireYear;
}
};
else if (field.equals("salary")) {
return new Comparator<Employee>() {
public int compare(Employee p1, Employee p2) {
return (int) (p1.salary - p2.salary);
}
};
}
return null;
}
}


You might protest that this is overly complicated, and that this will do the job:

public Comparator<Employee> getComparatorFor(final String field) {
return new Comparator<Employee>() {
public int compare(Employee p1, Employee p2) {
if (field.equals("name"))
return p1.name.compareTo(p2.name);
else if (field.equals("hireYear"))
return p1.hireYear - p2.hireYear;
else if (field.equals("salary"))
return (int) (p1.salary - p2.salary);
else
// return what? everything is a legal value!
}

}
}

but this one won't work because you must have a return, and returning any value is mis-leading (every possible integer here means something: 1 for greater than, 0 for equal, -1 for less than). So, you are left with the bigger version. I've actually attempted to optimize this several ways (with more generics, reflection, etc. but I always get defeated by the requirement to return a meaningful int from the comparison method. You could find some kind of minor optimization, but you are still building structure to solve the problem: a class per comparison strategy. This is the typical structural approach to solving this problem.

Here's the same solution in Ruby, using a similar Employee class:

class Array
def sort_by_attribute(sym)
sort {|x,y| x.send(sym) <=> y.send(sym) }
end
end

In this code, I'm taking advantage of several Ruby features. First, I'm applying this to the Array class, not a separate ComparatorFactory. Open classes allow me to add methods to built-in collections. Then, I'm taking advantage of the fact that Ruby method calling semantics are message based. A method call in Ruby is basically just sending a message to an object. So, when you see x.send(sym), if sym has a value of age, we're calling x.age, which is Ruby's version of the accessor for that property. I'm also taking advantage of the Ruby "spaceship" operator, which does the same thing that the Java compare method does: return a negative if the left argument is less than the right argument, 0 if they are equal, and positive otherwise. Wow, it's nice to have that as an operator. I should add that to Java...oh, wait, I can't. No operator overloading.

Perhaps it makes you squeamish to add this method to the Array class (open classes seem to terrify Java developers a lot). Instead, in Ruby, you could add this method to a particular instance of array:

employees = []
def employees.sort_by_attribute(sym)
sort {|x, y| x.send(sym) <=> y.send(sym)}
end

Now, the improved employees array (and only this instance of array) has the new method. A place to put your stuff, indeed.

When you have a powerful enough language, many of the design patterns melt away into just a few lines of code. The patterns themselves are still useful as nomenclature, but not as implementation details. And that's the real value. Unfortunately, the implementation seems to be the focus for many developers. Strong languages show you that the implementation details can be so trivial that you hardly even need the nomenclature (but its still useful as a conversation shortener.)

Next, I'll talk about language beauty.

15 comments:

Stephan Leclercq said...

>> open classes seem to
>> terrify Java developers a lot

No, they tend to make us laugh :-)


>> you could add this method
>> to a particular instance
>> of array

Whoa! You re-invented the concept of self modifying code. Nice! Do you think DynamicCobol will reinstate the ALTER x TO PROCEED TO y statement as a fully accepted software construct? I miss that feature!

Sidu said...

My epiphany came through the realisation that I no longer needed the Strategy Pattern in Ruby.
However, the (very legitimate) fear that Java/C# devs have of dynamic languages stems from the fact that they can't trust people to write good code, not from the dynamism of the language.
Imagine random modifications to class behaviour at runtime, usually not backed up and documented by any unit tests. It'd all be very hard to trace and debug.
Developing in large teams when using a dynamic language requires a very high level of self-discipline. I'd unhesitatingly predict doom and disaster for any dynamic language project with > 15 people and without a rock solid suite of tests and a good continuous integration pipe.

Michael Harrison said...

It's been a while since I coded a working line of Java, but... in your second example of the Java Comparator<Employee>, couldn't you cover the "else" case by throwing an exception that communicates "my object doesn't have a field by that name"? Of course, now you have to catch (or rethrow) the exception in the code that uses the comparator object, but you do get to have more compact and readable code.

Again, I may be missing something, because I too have turned away from Java for less masochistic languages. I'd be interested to know.

mh

Bob McCormick said...

Recipe's are bad? I guess you're not a fan of Chad Fowler's
Rails Recipes book? Or the Ruby Cookbook?

IMHO, recipes can be very valuable teaching tools. The problem with the Java pattern community isn't because patterns or recipes are bad pre se. The problem I think is two fold:

1) There seems to be an abundance of
Cargo Cult programmers
in the Java community.

2) Java as a language sucks. It's cumbersome, over verbose, and non-expressive.

Personally, I think Java is like a black hole of suckitude. Everything that gets near it get's sucked into a vortex of baroque overcomplexity and "enterprisyness".

taw said...

employees.sort_by(&:foo) will do the job without any extra definitions. (assuming Rails core extensions)

Matt Gillooly said...

I think the even more Ruby-ish way to acchieve this is to define Symbol#to_proc, as in Rails...

class Symbol
def to_proc
proc { |obj, *args| obj.send(self, *args) }
end
end

Prepending the symbol with an ampersand will call to_proc on it, which you can then pass into Emumerable#sort_by...

employees.sort_by &:hire_year

Logan Capaldo said...

a.sort_by { |o| o.name }
a.sort_by { |o| o.age }

Not only shorter and clearer, but cheaper.

benstiglitz said...

There’s also the magical symbol-to-proc hack, which lets you do a.sort_by(&:attribute).

chubbard said...

Well, but the structure of the strategy pattern is still preserved in the Ruby version in that the block you're sending can be swapped out with different ways to implement comparison. The block is the strategy because that is the structure that allows you to externalize the comparison logic. Not the implementation of the block.

The fact that you can do the slick send(sym) call and use the spaceship operator has little to do with the Strategy pattern itself. It's really just an interesting, tiny ;-) implementation of the strategy pattern.

The closest thing I can think of in Java requires no reflection which goes something like:

public class ComparableComparator {
public int compare( Object o1, Object o2 ) {
return ((Comparable)o1).compareTo( (Comparable)o2);
}
}

Exception handling be gone, but you get the idea.

Remember that objects are just the poor man's closures. And, closures are just the poor man's object.

Saint said...

This isn't really difficult to do in Java using reflection. For example:

public class EmployeeSorter implements Comparator < Employee > {
private Method method;

public EmployeeSorter(String methodName) throws NoSuchMethodException {
this.method = Employee.class.getMethod(methodName, null);
}

public int compare(Employee o1, Employee o2) {
Object field1, field2;
try {
field1 = method.invoke(o1, null);
field2 = method.invoke(o2, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return ((Comparable) field1).compareTo(field2);
}
}

Here's an example usage of the sorter:

Employee e1 = new Employee("Mark St. John", 2007, 1000);
Employee e2 = new Employee("Meme Agora", 2006, 2000);
EmployeeSorter es = new EmployeeSorter("getName");
System.out.println(es.compare(e1, e2));

Slava Pestov said...

Stephan Leclercq: something tells me you don't really know what self-modifying code is.

Defining a method on an instance is no more 'self-modifying' than storing a Command pattern implementation in an instance variable of a Java class.

Stephan Leclercq said...

Slava Pestov: There is however a big difference. When I define a Strategy instance variable, at least I communicate my intent; the "statefulness" is now obvious. If you allow anyone to add/redefine a method on an object, you impliclitly add a hidden state in the object. Read again: the keyword here is hidden. In other terms, simply reading the source code of a class definition does not allow you to accurately predict behaviour, because you would expect a method to be called, but instead, another will be called (not always the same, depending on the previous path of execution, and once again not obvious from reading the source code.) That is clearly the definition of self modifying code.

Blaine said...

The "Design Patterns: Smalltalk Companion" book does touch on using meta techniques to implement certain patterns. They also show the trade-offs when they do show them. Also, "Guide To Better Smalltalk: a Sorted Collection" dives into using meta techniques. I think most Rubyists would get a lot out of both books. There's a lot of good advice for dynamic language developers in both books.

lojic said...

Something to keep in mind from the pickaxe: "Internally, sort_by generates an array of tuples containing the original collection element and the mapped value. This makes sort_by fairly expensive when the keysets are simple."

Rakesh said...

My experience with open classes supports Sidu's comment. In principle I like them because they allow you to do useful and general things as in your example, but I know from experience that without a stringent code review process, they're dangerous. I ran into many situations where my code broke because of a poor developer adding stuff to a core class without telling anyone, that lead to a lot of wasted time debugging mysterious and hidden problems.

Since Ruby has not compiler, I had no way of knowing that the problem was caused by a change in a base class that I happened to be using.