Monday, May 16, 2016

Procs and Lambdas: Closures in Ruby

In Ruby, almost everything is an object


In order to grasp closures, it's useful to understand some particularities of Ruby.

The Ruby documentation states that "In Ruby, everything is an object." Actually, it depends on our definition of "everything". Lucas Carlson provides a more accurate definition in his book Ruby Cookbook: "In Ruby, everything that can be assigned to a variable is an object".

There are two important exceptions to the "everything is an object" rule: blocks and methods are not objects..

Introduction to Closures


A closure is an object with the following characteristics:
  • It can access variables defined in the scope on which it was defined and remembers the values of all variables available in that scope, even when called on another scope or when those variables are no longer available (out of scope).
  • It is an object. Hence, it can be assigned to a variable, passed around, called from within different scopes, etc.

In contrast, a regular method can only be called from within the scope on which it was defined and can only access variables received as arguments.

Below is an example using a Lambda. The same could be done using a Proc.


def meditate
    minutes = 40
    return lambda { minutes }
end
 
p = meditate
minutes = nil
p.call # Output: => 40

Notice that, when we called the lambda, the meditate method was already gone. We also set the value of minutes to nil. Nonetheless, the lambda remembered the initial value of minutes.

Ruby has two types of closures: Lambdas and Procs.

Methods are not closures, this is explained in this post. Also, many authors state that blocks are closures, but they are not, as explained in this post.

Procs


Procs are very similar to blocks, with a few important differences:
  • Unlike blocks, procs are objects and can be assigned to variables.
  • Procs are closures, blocks are not (as seen above).
  • Unlike blocks, we can pass multiple Procs to a method as arguments.

Basic Syntax


Procs are defined by calling Proc.new and called with the call method. The most basic syntax for defining a proc is:


p = Proc.new { puts "Hello!" }
p.call # Output: Hello!

The syntax for multi-line procs is:

p = Proc.new do
    print "Hello! "
    print "Goodbye!"
end
p.call # Output: Hello! Goodbye!

Passing multiple procs to a single method


We can pass multiple procs to a single method as arguments.


def multi_proc_demo(p1, p2)
    p1.call
    p2.call
end

p1 = Proc.new { puts "Hello from the first proc" }
p2  = Proc.new { puts "Hello from the second proc " }

multi_proc_demo(p1, p2)
Output:

Hello from the first proc
Hello from the second proc

Procs can take arguments


Procs can also have parameters and take arguments.


p = Proc.new { |name| puts "Hello, #{name}!" }
p.call "Eihei Dogen" # Output: Hello, Eihei Dogen!

Multi-line syntax with arguments:


p = Proc.new do |name|
    puts "Hello, #{name}!"
end

p.call "Eihei Dogen" # Output: Hello, Eihei Dogen!

In the example below, when calling the meditate method, we will provide a proc that takes an argument called "name".


def meditate(p)
    p.call 'zazen'  # zazen is an argument passed by the method to the proc
end

meditate Proc.new {|name| puts "Today we will practice #{name}."}
Output:

Today we will practice zazen.

Unlike a method, when a proc expects an argument and we don't provide one, it runs normally and defaults the missing argument value to nil.


p = Proc.new { |name| puts "Hello, #{name}!" }
p.call # Output: Hello, !

A proc handles arguments with greater flexibility than a method or a lambda. In addition to defaulting missing argument values to nil, a proc also silently discards unexpected arguments, implicitly unpacks arrays of arguments, etc. This behavior can be useful in some cases, but it usually makes it harder to notice and debug argument mismatches.

Starting at Ruby 1.9, we can also use the following alternative syntax to call a proc:


p["Eihei Dogen"]
p.("Eihei Dogen")
p::("Eihei Dogen")
p === "Eihei Dogen"
p.yield "Eihei Dogen"

Even though Ruby provides multiple ways to call a proc, it's usually a good idea to stick with the plain old call method in order to write code that is easier to read for future maintainers.

Procs and return statements


Another important difference between a proc and a method or lambda is the way in which they handle the return statement. If a method is defined inside another method, the return statement in the inner method exits only from the inner method itself, then the outer method continues executing. The same goes for defining a lambda within a lambda, a lambda inside a method or a method within a lambda. However, when a proc is defined within a method, the return statement will exit from the proc as well as the outer (enclosing) method. Example:


def meditate
    puts "Adjusting posture…"
    p = Proc.new { puts "Ringing bell…"; return }
    p.call
    puts "Sitting still…"  # This is not executed
end 

meditate
Output:

Adjusting posture…
Ringing bell…

Notice how the last line of the method was not executed because the return statement within the proc has exited from both the proc and the enclosing method.

If we define a proc without an enclosing (outer) method and use a return statement, it will throw a LocalJumpError.


p = Proc.new { puts "Ringing bell…"; return }
p.call
Output:

Ringing bell…
LocalJumpError: unexpected return

This happens because when a return statement is reached within a proc, instead of returning from the context where it was called, it returns from the scope on which it (the proc) was defined. In the following example, a LocalJumpError happens because the proc is trying to return from the top-level environment, where it was defined.


def meditate p
    puts "Adjusting posture…"
    p.call
    puts "Sitting still…"  # This is not executed
end

p = Proc.new { puts "Ringing bell…"; return }

meditate p 
Output:

Adjusting posture…
Ringing bell…
LocalJumpError: unexpected return

Usually, it's not a good idea to use a return statement within a proc. Procs are usually passed around between methods and if the method on which the proc was defined has already returned, it will throw an exception. In the example below we could just remove the return statement. However, there are cases we actually need to return something. In the latter, it's probably best to use a lambda instead of a proc. We will see later that lambdas handle return statements in a different way, more like methods.

Below is another scenario involving return statement:


def zafu_factory
    # This method will return the following proc implicitly
    Proc.new { puts "Round black zafu"; return }
end

def meditate
    puts "Adjusting posture…"
    p = zafu_factory
    p.call
    puts "Sitting still…"  # This is not executed
end 

meditate
Output:

Adjusting posture…
Round black zafu
LocalJumpError: unexpected return

What just happened? The zafu_factory method created and implicitly returned a proc. Then, the proc was called by the meditate method and when the return statement within the proc was reached, it tried to return from the context on which it was defined (the zafu_factory method). However, zafu_factory already returned the proc and a method can only return once each time it's called. In other words, an exception was thrown because the zafu_factory method had already returned when the proc was called and tried to return a second time.

The next statement


If all we need is to exit prematurely from a proc, we can use a next statement.


p = Proc.new { puts "Ringing bell…"; next; puts "Throwing up zafus" }
p.call # Output: Ringing bell…

Notice that, in the example above, no LocalJumpError was raised. Also, the code after the next statement was never executed and, luckily, no zafus (meditation cushions) were thrown. Now let's try it inside a method:


def meditate
    puts "Adjusting posture…"
    p = Proc.new do
        puts "Ringing bell…"
        next
        puts "Throwing up zafus"
    end
    p.call
    puts "Sitting still…"  # This line *is* executed
end

meditate
Output:

Adjusting posture…
Ringing bell…
Sitting still…

Notice that:
  • The proc exits immediately when the next statement is reached (no zafus were thrown up).
  • When the proc exits, the execution of the enclosing (outer) method is resumed and the line below the proc (puts "Sitting still…") runs normally.

Differences between Proc and Block

  • Unlike blocks, procs are objects, hence they can be assigned to variables and passed around.
  • We can pass multiple Procs to a method, but only a single block.

Available methods in the Proc class


To see all available methods that can be called on a proc, which is always an instance of the Proc class, we can do this:


Proc.instance_methods.sort
=> [:!, :!=, :!~, :<=>, :==, :===, :=~, :[], :__id__, :__send__, :arity, :binding, :call, :class,
 :clone, :curry, :define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?, :extend,
 :freeze, :frozen?, :hash, :inspect, :instance_eval, :instance_exec, :instance_of?, 
 :instance_variable_defined?,  :instance_variable_get, :instance_variable_set, :instance_variables,
 :is_a?, :itself, :kind_of?, :lambda?, :method, :methods, :nil?, :object_id, :parameters, 
 :private_methods, :protected_methods, :public_method, :public_methods,:public_send,
 :remove_instance_variable, :respond_to?, :send, :singleton_class, :singleton_method, 
 :singleton_methods, :source_location, :taint, :tainted?, :tap, :to_enum, :to_proc, :to_s, 
 :trust, :untaint, :untrust, :untrusted?, :yield]

The following is a list of instance methods defined in the Proc class (not inherited from other classes or modules). Notice that when the instance_methods method receives the false flag, it ommits inherited instance methods.


Proc.instance_methods(false).sort 
=> [:[], :arity, :binding, :call, :curry, :lambda?, :parameters, :source_location, :to_proc, :yield]

Covering all those methods is outside the scope of this post. Information about them is available at the Ruby Documentation.

Lambdas


Lambdas are anonymous functions, which means they are functions with no name. They are objects and therefore can be stored inside variables and passed around.

Lambdas compared to procs


Lambdas are instances of the Proc class.


Proc.new { } # Output: => #<Proc:0x00000001295210@(irb):153>
lambda {}  # Output: => #<Proc:0x00000001312b48@(irb):154 (lambda)>

While lambdas and procs are similar in many ways, there are some important differences:
  • Return statement behavior: Like a method, a lambda can return a value using the return statement. A proc cannot do that. Also, when a return statement is reached within a lambda, it will return execution to the outer method on which it was defined. When a Proc encounters a return statement, it will exit from both the proc itself and the enclosing method (method on which the proc was defined).
  • Handling missing arguments: When a lambda expects arguments and we don't provide them, or we provide the wrong number of arguments, an exception is thrown. When a Proc expects an argument and doesn't receive it, it runs and defaults the missing argument value to nil.

Lambdas compared to methods


In his book The Ruby Programming Language, Yukihiro Matsumoto (the creator of Ruby, AKA Matz) explains "A proc is the object form of a block, and it behaves like a block. A lambda has slightly modified behavior and behaves more like a method than a block. Calling a proc is like yielding to a block, whereas calling a lambda is like invoking a method. "

As discusses above, lambdas are similar to methods in how they handle arguments and return statements.
The most important difference between lambdas and methods is that the former is a closure and the latter is not. In other words, lambdas can:
  • Be assigned to variables and passed around (as they are objects);
  • Access and remember the values of all variables in the outer scope (in which they were defined).

Basic Syntax


The most basic syntax for defining a lambda is:


l = lambda { puts "Hello" }
l.call # Output: Hello

Notice we are calling the built-in lambda method and passing it a block.

As of Ruby 1.9, a new syntax called "stabby lambda" is supported:


l = ->{ puts "Hello!" }
l.call # Output: Hello!

There is also the do ... end syntax, suited for multi-line lambdas:


l = lambda do |message|
    puts message
end

l.call "Hey there" # Output: Hey there

How lambdas handle arguments


Just like in methods, we can set parameters when defining a lambda and pass arguments when calling it.


l = lambda { |name| puts "Today we will practice #{name} meditation." }
l.call "zazen" # Output: Today we will practice Zazen meditation.

Same example in the "stabby" syntax:


l = -> (name) { puts "Today we will practice #{name} meditation." }
l.call "zazen" # Output: Today we will practice Zazen meditation.

Notice the absence of space between the stabby operator (->) and the name argument. As of Ruby 2.0 introducing a space has no effect, however, on Ruby 1.9 it is invalid syntax and will fail.

When a lambda expects arguments and we don't provide them, or we provide the wrong number of arguments, an exception is thrown.


l = lambda { |name| puts "Today we will practice #{name} meditation." }
l.call
ArgumentError: wrong number of arguments (given 0, expected 1)

We can use the arity method to find out the number of expected arguments:


l.arity  # Output: => 1

Just like methods, lambdas accept all of the following types of parameters/arguments:
  • Positional parameters (required and optional)
  • Single splat parameter (*);
  • Keyword parameters (required and optional);
  • Double splat parameter (**);
  • Explicit block parameter prefixed with ampersand (&).

The following examples illustrate the syntax of a lambda that takes multiple types of arguments.


# Stabby syntax
l = -> (cushion, meditation="kinhin", *room_items, time:, posture: "kekkafuza", **periods, &p) do
  p.call
end

# Regular syntax
l = lambda do |cushion, meditation="kinhin", *room_items, time:, posture: "kekkafuza", **periods, &p|
  p.call
end

l.call("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block, which is now a proc." }
Output:

Hello from inside the block, which is now a proc.

Lambdas handle arguments the same way as methods. There is a comprehensive explanation of all the above parameter/argument types in this post about methods

There is an exception though. In Ruby, we can pass a block as an implicit argument to any method and execute it with a yield statement. This is covered in this post about blocks. Lambdas are different as they will not accept a block as an implicit argument. However, as seen above, we can pass a block to a lambda as an explicit argument (prefixed with ampersand) and execute it with the call method. We can also pass procs and other lambdas to a lambda as arguments. Example:


lambda1 = -> { puts "Hello from lambda1" } 
lambda2 = ->(other_lambda) do
  other_lambda.call
  puts "Hello from lambda2"
end

lambda2.call lambda1
Output:

Hello from lambda1
Hello from lambda2

Different ways to call a lambda


Starting at Ruby 1.9, we can also use any of the syntax below to call a lambda and provide arguments:


l["Eihei Dogen"]
l.("Eihei Dogen")
l::("Eihei Dogen")
l === "Eihei Dogen"
l.yield "Eihei Dogen"

Even though Ruby provides multiple ways to call a lambda, using the call method will probably make the code easier to read.

Lambdas and return statements


Lambdas also handle return statements in the same way as methods. When reached, a return statement will exit from the lambda itself and return execution to the outer (enclosing) method. Return statements are optional and, just like methods, lambdas will implicitly return the last evaluated expression. This post about methods contains a longer explanation.

12 comments:

  1. Great Article, too much new to learn. Great Post. Keep it up.

    ReplyDelete
    Replies
    1. Thank you for reading and providing feedback.

      Delete
  2. We can also call lambda with yield method

    l.yield("Eihei Dogen")

    ReplyDelete
    Replies
    1. Thank you for pointing it out. I have already updated the post.

      Delete
  3. Note that you must use brace on

    l.("Eihei Dogen")

    l. "Eihei Dogen"
    l:: "Eihei Dogen"
    are unvalid.

    but you can remove space for
    l=== "Eihei Dogen"

    Personaly I will prefer
    l<== "Eihei Dogen"
    or
    l <- "Eihei Dogen"
    left arrow look more like a call operator (feed lambda with)
    === to me look more like an comparaison operator.
    but well syntax is syntax.

    ReplyDelete
    Replies
    1. Thanks for reading and commenting. I have never tried the l=== "Eihei Dogen", indeed it works.

      Delete
  4. Very interested for me, thanks. Please, can you show example where of usage lambda or proc is better then methods?

    ReplyDelete
    Replies
    1. Thank you for reading. It's hard to compare a proc to a method as they behave differently in some important ways (handling of arguments and return statements). A lambda, however, may be better than a method when we need to store it in a variable, pass it around, execute it in different scopes or "remember" the values of the variables which were available when the it was defined.

      Delete
  5. Perfect timing! I just finished a lesson on blocks from launchschool.com and your article helps solidify my understanding of closures. I have to admit though that the controversy around whether blocks are closures is a bit confusing for a beginner like me.

    Launchschool describes blocks as "one way Ruby implements the idea of a closure". Would you say that the previous statement is accurate?

    ReplyDelete
    Replies
    1. I'm glad to know the post was useful to you.

      Some (including me) say that a closure has to be an object. In other words, something we can assign to a variable, pass around, call from within different scopes and so on. Blocks are not objects, that is explained in this post. However, some other people say that being an object is not a requirement to be a closure.

      IMO the important thing is to know when to use a block, a proc, a lambda or a method. I wouldn't worry too much whether a block fits in the definition of a closure.

      Delete
  6. I'm glad you liked it. Thanks for reading.

    ReplyDelete