Higher Order Messaging in Ruby
Having played around with Ruby for some six months now, I feel confident enough to make my first post on the subject. It’s going be about Higher Order Messaging, and it’s more of an expansion on an excellent description by Nat Pryce. I’m writing this post partly because I feel my implementation is a tad better (less cumbersome, as one need not define a class for each new method), and partly because it feels like his post has gone largely unnoticed.
First of all, let’s talk about blocks. Anyone familiar with Ruby will have some knowledge of blocks. Blocks in Ruby is a way to pass a chunk of code as argument to a function; blocks are also full closures, so they may reference any variables that exist in the scope in which they were declared. An example of block usage:
[0,1,2,3,4].select { |val| val.nonzero? }
=> [1,2,3,4]
As you may know, Enumerable#select loops through a collection and returns a new collection containing those elements for which the block evaluates to true. So, the example above produces the array [1,2,3,4], since Numeric#nonzero? will return false for the first element in the array and true for the others.
I expect this is nothing new for most people. Often however, as in the above example, this syntax seems a little verbose.
Using Symbols
You may have come across a little method in the Ruby Extensions project named Symbol#to_proc. In fact, it seems like this hack soon will be an official part of Ruby. Now to achieve the same results as the example above, we can use,
[0,1,2,3,4].select(&:nonzero?)
=> [1,2,3,4]
which is, if nothing else, more compact. Don’t get me wrong, I love this hack. But some object that this syntax is a bit ugly, and I agree partly, so we want to do this some other way, if possible. By understanding how calling methods works in Ruby, we can construct an alternate syntax.
Using Higher Order Messaging
Higher order messaging consists of the idea that method calls (messages), can be passed as arguments. I’ll spoil the surprise and show right away how our new syntax will look:
[0,1,2,3,4].where.nonzero?
=> [1,2,3,4]
Looks good, doesn’t it? It looks like the method where takes a method call (nonzero?) as its argument, forwards it to each element and collects those for which it returns true. That is kind of what happens, but in a much more complicated way. Most of the magic lies in the where method; it returns an object that responds to nonzero?. The object that is returned from where is an instance of HigherOrderMessage:
class HigherOrderMessage
instance_methods.each { |method| undef_method(method) unless method =~ /__(.+)__/ }
def initialize(&proc)
@proc = proc
end
def method_missing(id, *args)
@proc.call(id, *args)
end
end
It might look a bit confusing, but it’s actually very simple. First note that all functions that aren’t vital (such as __send__) are unlinked, so they may not be called. Second, when constructing a HigherOrderMessage, a block must be supplied, which is stored in the object. Finally, method_missing is defined. This method is a bit special, because it’s called whenever another method doesn’t exist. The first parameter is the name of the method that was attempted; the second parameter is its arguments.
So far, this code does nothing. It’s a mere skeleton to build some functionality from. This is done by in the implemention of Enumerable#where:
module Enumerable
def where
HigherOrderMessage.new do |id, *args|
select { |x| x.__send__(id, *args) }
end
end
end
[0,1,2,3,4].where.nonzero?
=> [1,2,3,4]
Let’s dissect the example above; first where is called. What happens is that a new HigherOrderMessage object is created and returned (for brevity, this object will henceforth be referred to as hom). Furthermore, this object has one instance variable—@proc—that contains the code block that was passed to it.
Then, we’re trying to call hom.nonzero?. Since this method doesn’t exist, hom.method_missing(:nonzero?) is called instead. method_missing forwards the method id and its parameters to the block that is stored in @proc. This block consists of a call to select, which in our example evaluates to { |x| x.__send__(:nonzero?) }, equivalent to { |x| x.nonzero? }. Finally, the array that’s generated is returned.
Now you may realize why all defaults methods (those inherited from Object) were undefined: it’s simply so that all methods are forwarded to method_missing. Otherwise, some_array.where.frozen?, for example, wouldn’t work, because it would check whether the HigherOrderMessage object is frozen or not.
Some Other Methods
Let’s construct some other useful methods: do_each, do_collect and do_inject.
module Enumerable
def do_each
HigherOrderMessage.new do |id, *args|
each { |x| x.__send__(id, *args) }
end
end
def do_collect
HigherOrderMessage.new do |id, *args|
collect { |x| x.__send__(id, *args) }
end
end
# Requires Enumerable#inject
def do_inject(start_value)
HigherOrderMessage.new do |id, *args|
inject(start_value) { |a,x| a.__send__(id, x, *args) }
end
end
end
a, b = [0,1], [1,2]
[a,b].do_each << 3
[a,b]
=> [[0, 1, 3], [1, 2, 3]]
[0,1,2,3,4].do_inject(0).+
=> 10
[0,1,2,3,4].do_collect * 2
=> [0,2,4,6,8]
The first line adds a new element, 3, to the array. The second like computes and returns the sum of the given array: 10. The third maps the message *(2) to all elements in the array. All examples demonstrate something that would have been impossible to do with Enumerable#to_symbol: when passing a method call you pass both the method name and its arguments, but when you pass a symbol that’s converted to a proc you can’t pass any arguments. Thus, you could never do [a,b].each(&:<<(3)).
Of course, even more complex stuff can be done. Here’s an example of a method that, seemingly, takes two methods as parameters.
module Enumerable
def having
HigherOrderMessage.new do |id, *args|
HigherOrderMessage.new do |secid, *secargs|
select { |x| x.__send__(id, *args).__send__(secid, *secargs) }
end
end
end
end
[0,1,2,3,4].having.succ < 3
=> [0,1]
The having method, apart from being very useful, demonstrates something interesting about higher order messaging: it can be chained. Not only does having return a HigherOrderMessage instance, even the block in @proc returns one! That means that [0,1,2,3,4].having.succ returns a HigherOrderMessage object that takes yet another function (in our case, <), applies succ to each element and checks it against <(3). Note that this is different than [0,1,2,3,4].succ.where < 3, which would return an array of successors rather than objects from the original array.
A warning: while operators like <, >, ==, and so on may be used in these functions, != mustn’t. From my understanding this is due to a != b being a short form of !(a == b). (E.g., [0,1,2,3,4].succ.where != 3 would expand to !([0,1,2,3,4].do_collect.succ.where == 3), which is false.
Final Words
This is not meant as a replacement for blocks in collection manipulation, but maybe a replacement for Symbol#to_proc. Higher order messaging has, in my opinion, the following advantages:
- The syntax less wordy than using a block.
- It’s more flexible than using
Symbol#to_procsince you’re effectively forwarding a method call rather than just a method.
It’s not without its problems though. First of all, I’d like to see a version where we can drop the “do_” part of the methods that already exist in Enumerable, and instead have a general function that can either take a block and work like normal, or not take one and behave like a higher order message. And secondly, the implementation itself: right now, this is more of a hack than anything else. It’s probably slower than using a block. And it’s not really tested. At all.
October 9th, 2006 at 12:22
Sir, this post has left me humbled and afraid. And a little peckish.
October 11th, 2006 at 22:14
Here’s an idea - let’s just modify the methods in Enumerable to accept arguments instead of adding extra syntax that does nothing more than shift the block to the left.
- Dan
October 11th, 2006 at 22:39
This is so lovely. I’ve never much liked Ruby’s block messaging syntax, despite being impressed by its power. I am very anxious to see how the community responds as this approach matures.
October 12th, 2006 at 19:11
Thanks! You teach me a lot.
Inspired by your work, I think it may be more natrual to keep ’select’ as the HOM name.
class Array
alias :old_select :select
def select(&p)
if block_given? # conventional call, followed by a block
old_select(&p)
else
HigherOrderMessage.new do |id, *args|
old_select { |x| x.__send__(id, *args)
end
end
end
So that we can say:
(1..100).to_a.select.odd?
October 12th, 2006 at 19:13
HTML support?
class Array
alias :old_select :select
def select(&p)
if block_given? # conventional call, followed by a block
old_select(&p)
else
HigherOrderMessage.new do |id, *args|
old_select { |x| x.__send__(id, *args)
end
end
end
October 12th, 2006 at 19:45
Thanks for the comments.
Daniel Berger:
I see what you mean. While the methods I wrote here were more meant to demonstrate what HOM is and what it can do, it’s true that blocks fills the same functionality and more. It’s still an interesting design pattern, if you ask me. So to clarify: using these specific methods in code for a real project is not something I advise, but learning what HOM is, that can be useful.
Mike Meng:
It might be a good idea; however, I’ve got something against overriding methods. So for this example I decided to make new methods instead. Oh, and by the way, I’m pretty much running default Wordpress, so markup in comments are a bit lacking (in fact, I don’t think any kind of markup is allowed). Sorry about that.
October 13th, 2006 at 05:15
Thanks. I agree your consideration. Since do_each, do_collect, do_inject and (if ‘do_select’ is used instead of ‘where’) do_select share almost same code, I suggest :
class Array
instance_methods.select {|m| [:select, :each, :collect, :inject].include? m.to_sym}.each {|m|
module_eval
October 13th, 2006 at 05:17
Hi Istarius, I’m not complaining, but your comment template system is REALLY broken!
Try paste my code again:
class Array
instance_methods.select {|meth| [:select, :each, :collect, :inject].include? meth.to_sym}.each do |meth|
module_eval
October 13th, 2006 at 05:19
Faint, I give up.
October 13th, 2006 at 12:19
It’s the default system, I guess I should try to find something else that works. And that’s only one of the little quirks I dislike about WP, I might be looking to move on to something else soon…
Anyway, I see what you’re getting at (I believe), and that’s an excellent idea.
October 14th, 2006 at 21:11
Check out Dr. Nic’s recent work on map_by_method.
December 5th, 2006 at 15:50
Cool! That’s a really nice redesign, much better than the way I wrote it originally.
Do you want to contribute that into the Homer project on RubyForge?
December 8th, 2006 at 11:29
Cheap 17…
Site 17…
December 8th, 2006 at 12:49
Read 4…
You 4…
January 10th, 2007 at 06:27
About ftop…
Site ftop…
January 10th, 2007 at 07:58
My italy…
Read italy…
January 10th, 2007 at 09:32
Blog topnado…
My topnado…
January 10th, 2007 at 11:10
Popular top…
Cheap top…
February 11th, 2007 at 03:11
Hello, my name is Alex, i’m a newbie here. I really do like your resource and really interested in things you discuss here, also would like to enter your community, hope it is possible:-) Cya around, best regards, Alex!
April 23rd, 2007 at 03:12
[…] A friend of mine sent me this link a while back on Higher Order Messaging in Ruby, which tracks back to this original post on HOM in Ruby and eventually tracks back to a presentation given on HOM in Objective-C. […]
May 13th, 2007 at 12:11
Hi My Name Is ivatnn.
May 15th, 2007 at 18:05
ABC24…
chuj w dupe policji…
May 30th, 2007 at 23:44
Hello! Good Site! Thanks you! zmhfuhyztiajew
May 31st, 2007 at 22:51
suzuki chicago…
ka-ka-sh-ka 4036326 Value information about suzuki chicago…
June 1st, 2007 at 02:04
2007 jeep tj…
mazda miata roll bar…
June 3rd, 2007 at 20:20
wrizk
June 3rd, 2007 at 20:22
cvqvoi
June 4th, 2007 at 15:58
chat latin yahoo…
ka-ka-sh-ka 4036326 Advantages of chat latin yahoo….
June 7th, 2007 at 20:10
mature ebony nude…
ka-ka-sh-ka 4036326 Aggregator of mature ebony nude sites…
June 7th, 2007 at 20:46
nn amateur teen…
ka-ka-sh-ka 4036326 nn amateur teen moves…
June 7th, 2007 at 21:07
Porno Tie…
Free Newest XXX Video…
June 9th, 2007 at 15:39
nude asian porn…
Features of nude asian porn….
June 9th, 2007 at 23:17
free nude indian models…
Fresh news on free nude indian models….
June 11th, 2007 at 12:40
Higher Order Messaging in Ruby…
[…]Higher order messaging is a pattern that allows a more object-oriented approach of manipulating objects, by accepting a method call as a parameter to another method. A rough version of this can easily be implemented in Ruby.[…]…
June 12th, 2007 at 21:56
http://3.kikysexx.info x
July 5th, 2007 at 20:50
grassrootsyouth…
grassrootsyouth…
July 11th, 2008 at 20:53
http://www.clubsguide.com.au/forum/viewtopic.php?t=1967
January 7th, 2009 at 04:05
esure car insurance car insurance esure
January 9th, 2009 at 00:51
hi
enj90i3ljsv42yoa
good luck
January 10th, 2009 at 13:43
hi
enj90i3ljsv42yoa
good luck
March 29th, 2009 at 22:42
Interesting article, i have bookmarked your site for future referrence :)