Function Composition in Ruby
I’m sure several dozen other people have done this already, but I found it nifty. Often when I’m using the Rails console I do things like this:
Model.find(:all).map(&:something).map(&:others).map(&:size)
Not bad all in all. But, like most good things, it spoils you eventually.
Now I want it shorter… simpler… better! One thing I’ve always liked
about Lisp is function composition. Paul Graham’s Arc has it down pat,
making it as simple as func1:func2:func3 to compose
functions.
Doing simple function composition in Ruby is really quite effortless.
class Proc
def |(other)
lambda { |*args| self[other[*args]] }
end
end
add1 = lambda { |i| i + 1 }
sub3 = lambda { |i| i - 3 }
[1,2,3].map(add1|sub3) #=> [-1,0,1]
Hurray. We’re done! Or are we? Well, not quite. Notice in the initial code above, we’re using blockified symbols in the map calls. We’re going to do this part a little differently…
class Symbol
def <(other)
self.to_proc | other.to_proc
end
def >(other)
other.to_proc | self.to_proc
end
end
Well whats the point of all this? Well, traditionally functions composed
as a|b|c will end up as a( b( c( blah ) ) ).
However, if we were to do that with the initial example above, we’d up with
something along the lines of…
Model.find(:all).map &(:size|:others|:something)
Notice that they’re reversed from what they were when using the maps above? That might be fine, but it seems counter intuitive to me. In this case, I’d like to be able to write them in the reverse of that… but at the same time, the I’d like to keep the traditional ordering around. So, we’re using symbols which give a bit of direction.
So, using the code above, we could write our initial code as…
Model.find(:all).map &(:size>:others>:something)
Edit: Yeah, I don’t know what up with the semi-colons before the symbols in the example code. Something is screwing it up… Sorry!