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 theing 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!
February 14, 2008 — Tyler McMullen
Source: http://web.archive.org/web/20101228224741/http://drmcawesome.com/FunctionCompositionInRuby