← Back to the homepage

A Guide to Function Composition in Ruby

by Paul Mucur on

The release of Ruby 2.6 in December 2018 included the following “notable new feature”:

Add function composition operators << and >> to Proc and Method. [Feature #6284]

In this post, I will explain “function composition” and how these new operators work, including how you can use them not just with Proc and Method but with any object that implements call.

  1. What is function composition?
  2. Functions in Ruby
  3. The Ruby feature proposal
  4. Function composition in Ruby
  5. Further reading

What is function composition?

Before we get into Ruby’s implementation of function composition, let’s be clear what it actually means. Wikipedia describes function composition as follows:

In computer science, function composition is an act or mechanism to combine simple functions to build more complicated ones. Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole.

Let’s think of a simple example of a function: something that takes a single number as input and returns double that number, e.g.

double(x) = x * 2
double(2) = 4
double(3) = 6
double(4) = 8

Now let’s imagine another simple function that squares a number (that is, multiplies it by itself):

square(x) = x * x
square(2) = 4
square(3) = 9
square(4) = 16

If we wanted to first double a number and then square the result, we could call each function individually with our desired input, e.g.

double(2) = 4
square(4) = 16

As our functions only calculate their result from their given input and have no side-effects (see “referential transparency” for more information on this topic), we can combine them:

square(double(2)) = 16

For convenience, we could define a new function to do this operation for us:

double-then-square(x) = square(double(x))
double-then-square(2) = 16
double-then-square(3) = 36
double-then-square(4) = 64

Tada! We have composed the two functions double and square into a new one, the pithily-named double-then-square!

While composing functions yourself might seem relatively straightforward, some programming languages have a “first-class” notion of function composition which allow us to compose functions without having to define new functions ourselves. Perhaps the most concise example is Haskell’s function composition through the . operator:

doubleThenSquare = square . double

As in our previous example, this returns a new function which will call double and then call square with the result, returning the final result to us.

The order of operations might be confusing here: reading left to right, we first refer to square and then to double but when we call our doubleThenSquare function the order in which our component functions are called is in the opposite order. This is because the mathematical definition of function composition is as follows:

(g ∘ f)(x) = g(f(x))

This notation can be read “g after f” (or “g following f”) which might help when remembering the order of application when you call your composite function.

In a programming language where programs are largely written as a series of functions, having first-class function composition makes it easier for authors to compose behaviour from existing functions, possibly encouraging the breaking down of large functions into smaller, simpler parts.

So where does Ruby come into this?

Functions in Ruby

While we’ve been discussing functions in pseudocode, we need to establish the equivalent building blocks in Ruby. Ideally we want functions that we can pass as arguments to other functions, store in variables and data structures and be able to return them from other functions: in other words, we want first-class functions.

The first obvious Ruby equivalent is Proc, described in the Ruby documentation as follows:

A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called. Proc is an essential concept in Ruby and a core of its functional programming features.

We can create a Proc in a surprising number of ways:

# Use the Proc class constructor
double = Proc.new { |number| number * 2 }

# Use the Kernel#proc method as a shorthand
double = proc { |number| number * 2 }

# Receive a block of code as an argument (note the &)
def make_proc(&block)
  block
end

double = make_proc { |number| number * 2 }

# Use Proc.new to capture a block passed to a method without an
# explicit block argument
def make_proc
  Proc.new
end

double = make_proc { |number| number * 2 }

(See “Passing Blocks in Ruby Without &block” for more information on this last constructor.)

We can use a Proc by calling its call method with any arguments we desire. There are also a few shorthand alternatives we can use for brevity:

double.call(2) # => 4
double.(2)     # => 4
double[2]      # => 4
double === 2   # => 4

Note that this last form is particularly useful when using a Proc in the when clause of a case statement as case statements evaluate their various branches by calling ===:

divisible_by_3 = proc { |number| (number % 3).zero? }
divisible_by_5 = proc { |number| (number % 5).zero? }

case 9
when divisible_by_3 then puts "Fizz"
when divisible_by_5 then puts "Buzz"
end
# Fizz
# => nil

Procs also come in an extra flavour: a “lambda”. These have their own constructors:

# Use Kernel#lambda
double = lambda { |number| number * 2 }

# Use the Lambda literal syntax
double = ->(number) { number * 2 }

The key differences between “lambdas” and “procs” (that is, non-lambdas) are as follows:

We can demonstrate these differences with the following examples:

def lambda_with_bad_arguments
  lambda = ->(x, y) { "#{x} and #{y}" }
  lambda.call([1, 2, 3])
end

lambda_with_bad_arguments
# ArgumentError: wrong number of arguments (given 1, expected 2)

def lambda_return
  lambda = -> { return }
  lambda.call

  "This will be reached"
end

lambda_return
# => "This will be reached"

def proc_with_bad_arguments
  proc = proc { |x, y| "#{x} and #{y}" }
  proc.call([1, 2, 3])
end

proc_with_bad_arguments
# => "1 and 2"

def proc_return
  proc = proc { return }
  proc.call

  "This will not be reached"
end

proc_return
# => nil

The other Ruby feature that we can use as a first-class function is Method. This allows us to represent methods defined on objects (e.g. with def) as objects themselves:

class Greeter
  attr_reader :greeting

  def initialize(greeting)
    @greeting = greeting
  end

  def greet(subject)
    "#{greeting}, #{subject}!"
  end
end

greeter = Greeter.new("Hello")
greet = greeter.method(:greet)
# => #<Method: Greeter#greet>

These can then be called in exactly the same way as Proc:

greet.call("world") # => "Hello, world!"
greet.("world")     # => "Hello, world!"
greet["world"]      # => "Hello, world!"
greet === "world"   # => "Hello, world!"

Finally, some Ruby objects can transform themselves into Proc by implementing to_proc. Ruby will automatically call this method on any object that is being passed as a block argument to another method with &.

[1, 2, 3].select(&:even?)
# is equivalent to
[1, 2, 3].select(&:even?.to_proc)

Ruby provides implementions of to_proc for the following objects out of the box:

{ name: "Alice", age: 42 }.to_proc.call(:name)
# => "Alice"

:upcase.to_proc.call("hello")
# => "HELLO"

(For more information about implementing to_proc on objects, see “Data Structures as Functions (or, Implementing Set#to_proc and Hash#to_proc in Ruby)”.)

The Ruby feature proposal

In 2012, Pablo Herrero proposed that Ruby should offer composition for Proc with the following example:

It would be nice to be able to compose procs like functions in functional programming languages:

to_camel = :capitalize.to_proc
add_header = ->val {"Title: " + val}

format_as_title = add_header << to_camel << :strip

instead of:

format_as_title = lambda {|val| "Title: " + val.strip.capitalize }

Herrero provided a pure Ruby implementation which would add << as a method on Proc and behave as follows:

double = ->(number) { number * 2 }
square = ->(number) { number * number }
double_then_square = square << double
double_then_square.call(2)
# => 16

That same day, a debate over the exact syntax Ruby should adopt for this feature began. Some argued for +, others for * (as a close approximation of Haskell's aforementioned syntax) and some for |. In October that year, Matz weighed in:

Positive about adding function composition. But we need method name consensus before adding it? Is #* OK for everyone?

Unfortunately, consensus was not forthcoming (with <- being added to the debate) over the next two years.

In 2015, after spending some time doing functional programming in Clojure and finding the dormant proposal, I contributed a series of patches to Ruby to implement composition on Proc and Method. Keen to progress the debate, I picked * as the operator:

double_then_square = square * double
double_then_square.call(2)
# => 16

Herrero responded by proposing syntax inspired by F#’s function composition:

It would be nice to be able to compose functions in both ways, like in F#, you can do g << f or g >> f, sadly this was rejected before.

These two operators would allow for both “backward” composition with << and “forward” composition with >>.

“Backward” composition maps to the mathematical operator we discussed earlier so g << f is the same as g ∘ f meaning that calling the resulting composite function with an input x will call g(f(x))

double_then_square = square << double
double_then_square.call(2)
# => 16

“Forward” composition is the opposite of the above so g >> f is the same as f ∘ g meaning that calling the resulting composite function with an input x will call f(g(x))

double_then_square = double >> square
double_then_square.call(2)
# => 16

It might be helpful to think of the direction of << and >> as indicating how results flow from f to g and vice versa, e.g. the result of f is passed to g in g << f whereas the result of g is passed to f in g >> f.

Another three years passed and the feature was finally merged into Ruby 2.6 by Nobuyoshi Nakada who took my original patches and changed the operator from * to << and >>.

Function composition in Ruby

So now we know what function composition is and how it got into Ruby 2.6, how does it work?

There are two new operators:

Here are some examples of usage with our existing double and square:

double_then_square = square << double
double_then_square.call(2) # => 16
# or
double_then_square = double >> square
double_then_square.call(2) # => 16

These operators are implemented on both Proc (regardless whether they are lambdas or procs) and on Method.

Internally, the way composition works regardless whether you’re using << or >> is to create a new Proc (preserving whether the receiver is a lambda or not) that composes our functions for us. The entire feature is roughly equivalent to the following Ruby code:

class Proc
  def <<(g)
    if lambda?
      lambda { |*args, &blk| call(g.call(*args, &blk)) }
    else
      proc { |*args, &blk| call(g.call(*args, &blk)) }
    end
  end

  def >>(g)
    if lambda?
      lambda { |*args, &blk| g.call(call(*args, &blk)) }
    else
      proc { |*args, &blk| g.call(call(*args, &blk)) }
    end
  end
end

class Method
  def <<(g)
    to_proc << g
  end

  def >>(g)
    to_proc >> g
  end
end

This means we can compose Proc and Method objects in various configurations:

(double >> square).call(2)
# => 16

(double >> square >> Kernel.method(:puts)).call(2)
# 16
# => nil

(Kernel.method(:rand) >> square >> Kernel.method(:puts)).call
# 0.010775469851890788
# => nil

It also means that if you start with a lambda, composing to the left and the right will always produce a new lambda:

square.lambda?
# => true

not_a_lambda = proc { nil }
not_a_lambda.lambda?
# => false

(square >> not_a_lambda).lambda?
# => true

(square << not_a_lambda).lambda?
# => true

Similarly, starting with a non-lambda Proc or a Method will never give you a lambda back regardless of the right-hand operand:

(not_a_lambda >> square).lambda?
# => false

(not_a_lambda << square).lambda?
# => false

We can also take advantage of calling to_proc on Ruby objects and compose these with regular Proc objects:

(
  { name: "Alice", age: 42 }.to_proc >>
  :upcase.to_proc
).call(:name)
# => "ALICE"

The other key thing to note when composing is that the argument can be a Proc, a Method or anything that responds to call. This allows us to compose our own objects with Proc:

class Greeter
  attr_reader :greeting

  def initialize(greeting)
    @greeting = greeting
  end

  def call(subject)
    "#{greeting}, #{subject}!"
  end
end

(
  :upcase.to_proc >>
  Greeter.new("Hello") >>
  Kernel.method(:puts)
).call("world")
# Hello, WORLD
# => nil

Note this only works if your receiver is a Proc or a Method though, so you couldn’t put Greeter first in the chain above (as it does not implement >>).

Further reading

If you want to read more about example use cases for function composition in Ruby, please see the following blog posts:

It’s also interesting to look at the use of function composition in other languages:

Paul Mucur is a software development consultant at Ghost Cassette, open source maintainer and Ruby and Ruby on Rails contributor.