Higher Order Messaging in Ruby
I've recently been experimenting with Higher Order Messaging, a design pattern that can be used for querying and manipulating collections of objects in a purely object-oriented manner.
On the way back from JAOO last week I whipped up an implementation in Ruby and applied it to some of my Ruby code. I was very pleased with the result. Higher Order Messaging transformed laborious code that queried and manipulated objects in collections into succinct statements that clearly expressed the application rules being implemented.
What is a Higher Order Message?
A higher order message is a message that takes another message as an "argument". It defines how that message is forwarded on to one or more objects and how the responses are collated and returned to the sender. They fit well with collections; a single higher order message can perform a query or update of all the objects in a collection.
It's probably easiest to look at an example.
The following code allocates government benefits to claimants. To start, here's the definition of a benefit claimant.
class Claimant attr_reader :name attr_reader :age attr_reader :gender attr_reader :benefits def initialize(name, age, gender) @name = name @age = age @gender = gender @benefits = 0 end def retired? @gender == :male && age >= 65 || @gender == :female && age >= 60 end def receive_benefit(amount) @benefits = @benefits + amount end end
In the following code snippets, the variable
claimants
contains an array of Claimant objects.
Let's compare different ways of implementing the following business rule: retired claimants receive benefits of £50 a week.
Here's how to implement this rule by explicitly iterating over the list of claimants using Ruby's for statement:
for claimant in claimants if claimant.retired? then claimant.receive_benefit 50 end end
Here's the same rule implemented with higher-order functions - methods that accept a block:
claimants.select {|e| e.retired?}.each {|e| e.receive_benefit 50}
And finally here's the same rule implemented with higher order messages:
claimants.where.retired?.do.receive_benefit 50
I think that the code using higher order messages most succinctly expresses the business rule being executed. It expresses what is being performed and hides the details of how. In comparison, the code with explicit iteration mingles business logic within procedural control flow and the code using blocks has a lot of syntactic noise and additional block parameters.
The higher order messaging version does have messy dots between messages, but unfortunately that's an aspect of Ruby we can't change. At the risk of sounding like a frothing evangelist, I have to admit that the code would be neater in Smalltalk:
claimants where retired do receiveBenefit: 50.
I'd also prefer to use the name "each" instead of "do", but the name "each" has already been grabbed by the Ruby Enumerable mixin.
Anyway...
In the example above there are two higher order messages,
where
and do
. The where
message returns an array containing those elements of a collection
for which the following message returns true. The do
message sends the following message to all elements of a list and
returns nil
. The clause
"claimants.where.retired?" returns a list of claimants
that are retired, and that list is then sent
"do.receive_benefit 50". Thus, the single line means the
same as:
retired_claimants = claimants.where.retired? retired_claimants.do.receive_benefit 50
Higher order messages provide a convenient framework for querying and manipulating collections of objects that has the additional benefit of clearly expressing domain logic. Thanks to Ruby's open classes, I added the higher order messages to the Enumerable module, automagically defining them for Ruby's built-in arrays and all other enumerable classes.
More Useful Higher Order Messages
I've found the following higher order messages to be useful when working with collections.
Having filters a collection by a predicate applied to the result of a query sent to each element. This actually combines two higher order messages. For example, to give an additional benefit to claimants older than 100:
(claimants.having.age > 100).do.receive_benefit 25
Unless is the opposite of "where"; it returns the list of elements for which a predicate returns false:
working_claimants = claimants.unless.retired?
In_order_of and in_reverse_order_of sort on the value of an attribute:
sorted = claimants.in_reverse_order_of.benefits
Sum calculates the total of an attribute:
total_benefits = claimants.sum.benefits
Extract returns a collection containing the values of an attribute of the elements:
names = claimants.extract.name
Limitations of Higher Order Messages
Of course, there are limitations to what you can do with higher
order messages. Most obviously, you can only pass a single message
to a higher order message. This means, for example, that unlike the
Array#select
method, which accepts a block, the
higher-order ?where
message cannot accept a general
logical expression as a predicate to filter the collection.
Instead, you must define the predicate as a method of the objects
in the collection.
On the other hand, it's impractical to move all the code that manipulates an object into methods of its class. Take, for example, a report of benefits per claimant that is implemented as follows using blocks.
claimants.sort {|c1,c2| c2.benefits <=> c1.benefits}.each do |c| puts "#{c.name}\t#{c.benefits}" end
Implementing that report with higher order messages would require adding a "write_benefit_report_line" method to the Claimant class. This would mix presentation logic with application logic and reduce the cohesion of the Claimant class: not a good design decision.
To avoid this problem, I added a method to Enumerable that returns a collection of adapter objects that wrap the elements of the original collection. To write the benefits report above, I would now write a BenefitsReport class that can report the benefits for one claimant and create a collection of BenefitsReport objects from a collection of Claimants, as follows:
class BenefitsReport def initialise(claimant) @claimant = claimant end def display puts "#{@claimant.name}\t#{@claimant.benefits}\n" end end claimants.in_reverse_order_of.benefts.as(BenefitsReport).display
But are these really limitations? After using higher order messages for a while I've come to think that they are not. The first limitation encourages you move logic that belongs to an object into that object's implementation instead of in the implementation of methods of other objects. The second limitation encourages you to represent application concepts as objects rather than procedural code. Both limitations have the surprising effect of guiding the code away from a procedural style towards better object-oriented design.
Higher Order Messaging in Other Languages
It's easy to implement Higher Order Messaging in any dynamically typed OO language that can capture unknown messages. It was originally written in Objective C and Smalltalk and I wrote the Ruby implementation in only a couple of hours on the plane. I've described the implementation details in another post. I think this pattern would be most useful in languages that do not have a succinct syntax for higher order functions, such as Python.
I also tried, and failed, to implement higher-order messaging in
Java 5 using the java.lang.reflect.Proxy
class. If
anybody can implement higher-order messaging in Java I'll be
very impressed. Please let me know if you do.
Update: I've written a short post about how to implement this in Ruby.
Update: The code is available on RubyForge in the Homer project for anybody who wants to play with it.