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
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
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.
Great Article, too much new to learn. Great Post. Keep it up.
ReplyDeleteThank you for reading and providing feedback.
DeleteWe can also call lambda with yield method
ReplyDeletel.yield("Eihei Dogen")
Thank you for pointing it out. I have already updated the post.
DeleteNote that you must use brace on
ReplyDeletel.("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.
Thanks for reading and commenting. I have never tried the l=== "Eihei Dogen", indeed it works.
DeleteVery interested for me, thanks. Please, can you show example where of usage lambda or proc is better then methods?
ReplyDeleteThank 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.
DeletePerfect 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.
ReplyDeleteLaunchschool describes blocks as "one way Ruby implements the idea of a closure". Would you say that the previous statement is accurate?
I'm glad to know the post was useful to you.
DeleteSome (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.
superb info..
ReplyDeleteI'm glad you liked it. Thanks for reading.
ReplyDeleteExcellent informative blog, Thanks for sharing.
ReplyDeleteWeb Design Training
Great Article. Thank you for sharing! Really an awesome post for every one.
DeleteA Review on Blockchain Technologiesfor an Advanced and Cyber ResilientAutomotive Industry Project For CSE
A Review on the Application ofBlockchain to the Next Generation ofCybersecure Industry 4.0 SmartFactories Project For CSE
A Solution for Secure Certified Electronic MailUsing Blockchain as a Secure Message Board Project For CSE
A Blockchain based Internet ServiceArchitecture for Neuro informatics Project For CSE
Autonomous Resource RequestTransaction Framework Based onBlockchain in Social Network Project For CSE
Big Production Enterprise Supply ChainEndogenous Risk ManagementBased on Blockchain Project For CSE
Blockchain based Secure andTrustworthy Internet of Things inSDN Enabled 5G VANETs Project For CSE
Supervisory Control of Blockchain Networks Project For CSE
ReplyDeleteTeaTv Apk Is Another One Of The Best Online Streaming Application For Smartphone Users. This Application Is Available For Android & IOS Users. Also, You Can Connect This Application Easily With Amazon Firestick, Android Smart TVs, Start Settop Box, Etc. There are more apk to online streaming is Mediabox apk for smartphone users ( Android and ios ) and
Best Wishes Quotes
Watch Free online Movies
onhax
onhax android
Hulu Apk