Skip to content

Instantly share code, notes, and snippets.

@charly-vega
Last active March 27, 2017 04:50
Show Gist options
  • Save charly-vega/f01e99b915a1b1ea47e329a458d7c0b6 to your computer and use it in GitHub Desktop.
Save charly-vega/f01e99b915a1b1ea47e329a458d7c0b6 to your computer and use it in GitHub Desktop.

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 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment