Refactoring Higher Order Messaging

Delivery of mail by motorbike

In my experiments with higher order messaging I implemented convenient methods for selecting elements of a collection by queries sent to those elements: "where", "unless" and "having". These are equivalent to various uses of the Enunmerable#select method but, I think, much more readable.

While working on the Ruby code that used higher order messages I found I needed an equivalent to the Enumerable#any? and Enumerable#all? methods, which query if a collection contains any or all elements that match a predicate. Using higher order messages, I wanted to write statements like those below.

if claimants.all_where.retired? then
   ...
end

if claimants.any_having.benefits > 0 then
    ...
end

It was possible to write those higher order messages, of course, but only with a lot of duplicated logic. I got around this by splitting the higher order message objects into two: one object collects of results from individual elements, the other collates those results into a single total. The collector objects are higher order messages that define predicates over elements. They are common between all collators. The collators define whether those predicates are used to select a subset of the collection, or are combined by logical conjunction (all) or logical disjunction (any). Collectors are created by collators, like so

retired = claimants.select.where.retired?

if claimants.any.having.benefits > 0 then
    ...
end

The only problem now was that the select method is already defined by the Enumerable mix-in. I needed a new name. My solution was to change the naming convention of the higher order messages and as a by product allow a more natural, declarative expression of what was being calculated. In the new style, the example above looks like:

retired = claimants.that.are.retired?

if claimants.any.have.benefits > 0 then
    ...
end

Very expressive, but perhaps a little too whimsical. Only time will tell.

With this refactoring, it's very easy to add new collators. The higher order messages are all defined in a base class and new collators can be created in only a few lines of code:

class Collator
  def initialize(receiver)
    @receiver = receiver
  end
  
  def are
    return HOM::Are.new(self)
  end
    
  def are_not
    return HOM::AreNot.new(self)
  end
  
  ...    
end
  
class That < Collator
  def apply(&block)
    return @receiver.select(&block)
  end
end
  
class All < Collator
  def apply(&block)
    return @receiver.all?(&block)
  end
end
  
class Any < Collator
  def apply(&block)
    return @receiver.any?(&block)
  end
end

class Are < HigherOrderMessage
  def method_missing(id, *args)
    return @handler.apply {|e| e.__send__(id,*args)}
  end
end

class AreNot < HigherOrderMessage
  def method_missing(id, *args)
    return @handler.apply {|e| not e.__send__(id,*args)}
  end
end

Well, that's enough higher order messaging. The code is available on RubyForge in the Homer project for anybody who wants to play with it.

Posted on October 14, 2005 [ Permalink | Comments ]