Refactoring Higher Order Messaging
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.