← Back to the homepage

A Guide to Function Composition in Ruby

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 .

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 Proc s 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: Calling return from a lambda will only exit from the lambda whereas calling return from a proc will exit from the surrounding method (and will throw a LocalJumpError if there is no surrounding method)

from a lambda will only exit from the lambda whereas calling from a proc will exit from the surrounding method (and will throw a if there is no surrounding method) In lambdas, the number of arguments must match the definition exactly (as in a method definition with def ) but in procs, missing arguments are replaced with nil , single array arguments are deconstructed if the proc has multiple arguments and no error is raised if too many arguments are passed 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: Hash will return a Proc that takes a key as input and returns the corresponding value from the hash or nil if it is not found

will return a that takes a key as input and returns the corresponding value from the hash or if it is not found Symbol will return a Proc that takes an object and will call the method with the symbol’s name on it { 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)”.)

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: << : called “backward composition” in F# and “compose to left” in the Ruby source code

: called “backward composition” in F# and “compose to left” in the Ruby source code >> : called “forward composition” in F# and “compose to right” in the Ruby source code 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 >> ).

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

Ghost Cassette Ltd is registered in England and Wales with company number 11839376 and VAT registration number GB 316886961.

Registered office: 13b The Vale, London, England, W3 7SH