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.

Copyright © 2005 Nat Pryce. Posted 2005-10-14. Share it.