Saturday, October 13, 2007

Ruby Matters: Contracts vs. Promises

In teaching Ruby to Java developers, two things seem to shock and annoy developers: open classes and the lack of interfaces. Because Java developers are inundated with interfaces, it's hard to imagine a language whose libraries are strictly defined in this manner.

Interfaces in Java and .NET are exactly like legal contracts (where the compiler is the all-powerful judge). If you implement an interface yet don't meet the terms of the contract, the judge refuses to allow you to proceed.

Mixins in Ruby are more like promises than contracts. When you agree to pick up a friend at the airport, you don't sign a contract, you promise you'll be there. The punishment is purely social: if you break a promise, your friends won't do stuff for you. Here's an example.

In Ruby, a common Mixin is Comparable, which gives you the same behaviors as the Comparator interface in Java. The promise in Comparable is that you will implement the "spaceship" operator (<=>), which returns negative if the left-hand side is less than the right, 0 if they are equal, and positive if the LHS is greater than the RHS (which is exactly like the similar compare() method in Java).

class Employee
include Comparable

attr_accessor :name, :salary

def initialize(name, salary)
@name, @salary = name, salary
end

def <=>(other)
name <=> other.name
end
end

list = Array.new
list << Employee.new("Monty", 10000)
list << Employee.new("Homer", 50000)
list << Employee.new("Bart", 5000)

list.sort!
# Monty vs. Homer
list[0] < list[1] # => true

# Homer vs. Monty
list[0] > list[1] # => false

# Homer is between Bart and Monty?
list[1].between?(list[0], list[2]) # => true

If you violate the promise by mixing in Comparable and yet don't implement the spaceship, nothing bad happens...until you try to ask your friend Comparable to perform a service for you (like compare two things with between? or call the sort! method). Then, you get an error message complaining "undefined method `<=>' for Employee". And this happens at runtime, not at compile time.

This is a fundamentally different mindset than the legalistic strongly contracted languages. It demonstrates one of the reasons that you can't slack off on writing your unit tests in Ruby: you don't find out important things until runtime. It is certainly a different way of thinking about implementing APIs. While lots of Java developers think this leads to chaos in large development teams, I have not found that to be true (and I think that most Ruby developers would agree with my assessment). The trade-off is strict control vs. flexibility. If there is one thing that my experience has taught, it is that flexibility is the most important thing for strong programmers, echoed by many of Paul Graham's essays.

If you must have stronger contracts in Ruby, there is a framework called Handshake that allows you to create pre- and post-conditional invariants ala Eiffel. While I understand the appeal of Handshake, I would be loathe to use it on a project because I prefer the power of flexibility rather than the strictures of constraints.

2 comments:

Hamlet D'Arcy said...

I agree. Here's how I tried to persuade Java developers just yesterday:

Draw the classic Observer pattern in UML on a whiteboard. This is something that appears over and over in our design documents. With Ruby (or Groovy, for us) you get to erase one of the arrows and one of the boxes. Add closures/blocks instead of a method callback and the design is even simpler (one less arrow).

The point is that the resulting diagram is so simple that you never would put it in a design document. Your design process becomes oriented towards the function of what you're doing rather than the static structure (class diagrams).

Can we please put an end to pages of class diagrams in design documents and start thinking about the important stuff!

Brian Guthrie said...

Sorry for the late response on this one. I'm the author of Handshake (and a fellow TWer) and I just wanted to drop in to mention that straight-up pre- and post-conditions don't really appeal to me either, and they aren't at the heart of what I was going for with this framework.

The beauty of it (and of Ruby) is that you can develop flexible, domain-specific predicates, reusable and concisely expressed. It can be as simple as a check for some application-specific condition on a string (for example, "must begin with http://"), and it's useful because (a) you're not cluttering up your method bodies with this kind of code, and (b) you've assigned a domain-specific name to an object with these constraints. Check out the 'contract' method.

The framework isn't particularly useful for Rails projects, but might be useful if you were building something like Rails. I've been in situations in which having it around was handy and situations in which it's gotten in my way, so I suppose I'm myself somewhat undecided on its utility.

For more information on what I was going for, check out PLT Scheme's contract system: http://people.cs.uchicago.edu/~robby/plt-contracts-guide/