Blocks are widely used in Ruby gems (including Rails) and in well-written Ruby code. They are not objects, hence cannot be assigned to variables.
Basic Syntax
A block is a piece of code enclosed by { } or do..end. By convention, the curly brace syntax should be used for single-line blocks and the do..end syntax should be used for multi-line blocks.
{ # This is a single line block }
do
# This is a multi-line block
end
Any method can receive a block as an implicit argument. A block is executed by the yield statement within a method. The basic syntax is:
def meditate
print "Today we will practice zazen"
yield # This indicates the method is expecting a block
end
meditate { print " for 40 minutes." } # We are passing a block as an argument to the meditate method
Output:
Today we will practice zazen for 40 minutes.
When the yield statement is reached, the meditate method yields control to the block, the code within the block is executed and control is returned to the method, which resumes execution immediately following the yield statement.
Below is an example of a multi-line block:
meditate do
puts " for 40 minutes."
end
Output:
Today we will practice zazen for 40 minutes.
When a method contains a yield statement, it is expecting to receive a block at calling time. If a block is not provided, an exception will be thrown once the yield statement is reached. See example below:
meditate # Call method without passing a block
Today we will practice zazen.
LocalJumpError: no block given (yield)
We can make the block optional and avoid an exception from being raised:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end
meditate
Output:
Today we will practice zazen.
It is not possible to pass multiple blocks to a method. Each method can receive only one block.
Blocks can be useful in many different ways. Below is one example of a method that will measure the execution time of any piece of code.
def time_it
start_time = Time.now
yield
puts "Execution time: #{ Time.now - start_time } secs."
end
time_it { sleep 1 }
Output:
Execution time: 1.001391551 secs.
Blocks can take arguments
To pass an argument to a block, we have to pass the argument to the yield statement and specify its name at the block definition. The syntax is as follows:
def meditate
yield("zazen") # "zazen" is an argument passed from the method to the block
end
When calling the meditate method, we will provide a block that takes an argument called "name". Notice the expected argument is wrapped in | (pipe) characters.
meditate {|name| puts "Today we will practice #{name}."}
Output
Today we will practice zazen.
Blocks can also take multiple arguments, like this:
def meditate
yield("zazen", 40) # "zazen" and 40 are arguments passed from the method to the block
end
meditate {|name, time| puts "Today we will practice #{name} for #{time} minutes."}
Output:
Today we will practice zazen for 40 minutes.
Unlike methods and lambdas, blocks handle arguments with great flexibility. It silently defaults missing argument values to nil, discards unexpected (extra) arguments, unpacks arrays of arguments, etc.
Blocks can have return statements
The return statement in a block behaves differently than that of a method (or a lambda). If a method is defined inside another method, the return statement exits only from the inner method itself; then the outer method resumes execution at the following line. In contrast, if a block is defined within a method, a return statement within the block will exit the block itself as well as the enclosing method.
Example:
def meditate
puts "Adjusting posture…"
1.times { puts "Ringing bell…"; return }
puts "Sitting still…" # This is not executed
end
meditate
Output
Adjusting posture…
Ringing bell…
If we define a block without an enclosing (outer) method and include a return statement, it will throw a LocalJumpError.
1.times { puts "Ringing bell…"; return }
Output
Ringing bell…
LocalJumpError: unexpected return
This happens because when a return statement is reached within a block, instead of returning (exiting) from the method that yielded (called) the block, it will return from the scope on which it (the block) was defined. In the following example, a LocalJumpError happens because the block is trying to return from the top-level context, where it was defined.
def meditate
puts "Adjusting posture…"
yield
puts "Sitting still…" # This is not executed
end
meditate { puts "Ringing bell…"; return }
Output:
Adjusting posture…
Ringing bell…
LocalJumpError: unexpected return
Usually, it's not a good idea to use a return statement within a block. We can usually come up with different (better) ways to approach the problem. If we really need to return something, it's probably best to use a lambda. Lambdas handle return statements in a different way, more like methods.
If all we need is to exit prematurely from a block, we can use a next statement.
1.times do
puts "Adjusting posture…"
next # Exit block immediately
puts "Sitting still…" #This line is never executed
end
Output:
Adjusting posture...
Notice that the line below the next statement did not run.
Another example:
def meditate
puts "Adjusting posture…"
1.times { puts "Ringing bell…"; next; "Wreaking havoc at the zendo!" }
puts "Sitting still…" # This is not executed
end
meditate
Output:
Adjusting posture…
Ringing bell…
Sitting still…
Notice that no havoc was wreaked at the zendo but the line below the proc was executed.
Blocks and enumerators
The most common use of blocks is in enumerators.
In the examples below, note that all chunks of code enclosed by { } or do..end are blocks. Also, note that variable names enclosed by pipe characters (|) are arguments passed to the blocks.
a = ["zazen", "shamata", "tonglen", "vipassana"]
a.each { |meditation| puts meditation }
The example above can also be written in multi-line syntax using do and end instead of curly braces, like this:
a.each do |meditation|
puts meditation
end
Both the Array and the Hash classes provide a method called each, which returns an Enumerator object. The each method accepts a block as a parameter. At each iteration of the enumerator, one item of the array is passed into the block as an argument.
Blocks are not closures
A closure is a method with the following characteristics:
- It remembers the values of all variables that were available in the scope it was defined, even when called on another scope or when those variables are no longer available.
- It is an object. Hence, it can be assigned to a variable, passed around, called from within different scopes, etc.
This point is controversial; many authors state that blocks are closures. However, in the book The Ruby Programming Language
Blocks can be implicitly converted to procs with & (ampersand)
Blocks and procs are very similar. However, unlike a proc, a block is not an object and cannot be assigned to a variable.
A block can, however, be converted into a proc (which is a closure) when passed to a method with the argument name prefixed by & (ampersand). Example:
def meditate(&practice)
p = practice # Assign the proc to a variable. We would not be able to do that with a block.
p.call if block_given?
end
meditate { "40 minutes of zazen." }
Output:
=> "40 minutes of zazen."
In the example above, prefixing the practice argument with an & implicitly converts the passed block to a proc by calling the to_proc method. That's why we can assign it to the p variable and execute it with the call method instead of yield. We would not be able to do that with a regular block. As the argument prefixed with & is also used as the method's block, we could also execute it with a yield statement, like this:
def meditate(&practice)
yield
end
meditate { "40 minutes of zazen." }
Output:
=> "40 minutes of zazen."
Prefixing an explicit block argument with & will convert it to a proc. The opposite is also true: if we provide a proc as an argument prefixed with &, it will be converted into a block.
Great post, keep going!
ReplyDeleteThank you for reading!
DeletePerfect, thanks!
ReplyDeleteI'm glad you liked it. Thanks for your feedback.
DeleteI've recently found myself going down the rabbit hole of learning about closures-blocks-procs-lambdas, etc. and your post has helped clarify some things and point me in the direction of other things to keep learning (further down the rabbit hole). :-) I haven't found the "zen place" yet for understanding blocks as closures or not, but I'm getting close. I'm researching for my own blog post, so I'll come back and let you know when it's out. Thanks for posting this!
ReplyDeleteHello, Skylar. I agree this one of the many rabbit holes in Ruby and Rails. I'm glad you found this post to be helpful. Do let me know once your post is published. Thanks for your feedback :)
Deletewhy don't you write any post recently, i'm really waiting for your posts. Please
DeleteHello godness Phu,
DeleteThank you for your comment. Sorry about not being able to write new posts. I have been studying full time in the last few months, trying to improve my Ruby on Rails skills. Maybe I'll write some RoR posts in the months to come. Best Regards.
Dengan menggunakan akun pro diyakini dapat meningkatkan persentasi kemenangan yang bisa anda raih. Berikut ini adalah cara cara untuk dapat menang besar bermain bandarq
ReplyDeleteasikqq
dewaqq
sumoqq
interqq
pionpoker
bandar ceme terbaik
hobiqq
paito warna terlengkap
syair sgp
Great Article. Thank you for sharing! Really an awesome post for every one.
ReplyDeleteA 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