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!

blog comments powered by Disqus