Blocks and yields in Ruby
Solution 1
Yes, it is a bit puzzling at first.
In Ruby, methods can receive a code block in order to perform arbitrary segments of code.
When a method expects a block, you can invoke it by calling the yield
function.
Example:
Take Person
, a class with a name
attribute and a do_with_name
method. When the method is invoked it will pass the name
attribute to the block.
class Person
def initialize( name )
@name = name
end
def do_with_name # expects a block
yield( @name ) # invoke the block and pass the `@name` attribute
end
end
Now you can invoke this method and pass an arbitrary code block.
person = Person.new("Oscar")
# Invoking the method passing a block to print the value
person.do_with_name do |value|
puts "Got: #{value}"
end
Would print:
Got: Oscar
Notice the block receives as a parameter a variable called value
. When the code invokes yield
it passes as argument the value of @name
.
yield( @name )
The same method can be invoked with a different block.
For instance to reverse the name:
reversed_name = ""
# Invoke the method passing a different block
person.do_with_name do |value|
reversed_name = value.reverse
end
puts reversed_name
=> "racsO"
Other more interesting real life examples:
Filter elements in an array:
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
# Select those which start with 'T'
days.select do | item |
item.match /^T/
end
=> ["Tuesday", "Thursday"]
Or sort by name length:
days.sort do |x,y|
x.size <=> y.size
end
=> ["Monday", "Friday", "Tuesday", "Thursday", "Wednesday"]
If the block is optional you can use:
yield(value) if block_given?
If is not optional, just invoke it.
You can try these examples on your computer with irb
(Interactive Ruby Shell)
Here are all the examples in a copy/paste ready form:
class Person
def initialize( name )
@name = name
end
def do_with_name # expects a block
yield( @name ) # invoke the block and pass the `@name` attribute
end
end
person = Person.new("Oscar")
# Invoking the method passing a block to print the value
person.do_with_name do |value|
puts "Got: #{value}"
end
reversed_name = ""
# Invoke the method passing a different block
person.do_with_name do |value|
reversed_name = value.reverse
end
puts reversed_name
# Filter elements in an array:
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
# Select those which start with 'T'
days.select do | item |
item.match /^T/
end
# Sort by name length:
days.sort do |x,y|
x.size <=> y.size
end
Solution 2
In Ruby, methods can check to see if they were called in such a way that a block was provided in addition to the normal arguments. Typically this is done using the block_given?
method but you can also refer to the block as an explicit Proc by prefixing an ampersand (&
) before the final argument name.
If a method is invoked with a block then the method can yield
control to the block (call the block) with some arguments, if needed. Consider this example method that demonstrates:
def foo(x)
puts "OK: called as foo(#{x.inspect})"
yield("A gift from foo!") if block_given?
end
foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)
Or, using the special block argument syntax:
def bar(x, &block)
puts "OK: called as bar(#{x.inspect})"
block.call("A gift from bar!") if block
end
bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)
Solution 3
It's quite possible that someone will provide a truly detailed answer here, but I've always found this post from Robert Sosinski to be a great explanation of the subtleties between blocks, procs & lambdas.
I should add that I believe the post I'm linking to is specific to ruby 1.8. Some things have changed in ruby 1.9, such as block variables being local to the block. In 1.8, you'd get something like the following:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Whereas 1.9 would give you:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
I don't have 1.9 on this machine so the above might have an error in it.
Solution 4
I found this article to be very useful. In particular, the following example:
#!/usr/bin/ruby
def test
yield 5
puts "You are in the method test"
yield 100
end
test {|i| puts "You are in the block #{i}"}
test do |i|
puts "You are in the block #{i}"
end
which should give the following output:
You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100
So essentially each time a call is made to yield
ruby will run the code in the do
block or inside {}
. If a parameter is provided to yield
then this will be provided as a parameter to the do
block.
For me, this was the first time that I understood really what the do
blocks were doing. It is basically a way for the function to give access to internal data structures, be that for iteration or for configuration of the function.
So when in rails you write the following:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
This will run the respond_to
function which yields the do
block with the (internal) format
parameter. You then call the .html
function on this internal variable which in turn yields the code block to run the render
command. Note that .html
will only yield if it is the file format requested. (technicality: these functions actually use block.call
not yield
as you can see from the source but the functionality is essentially the same, see this question for a discussion.) This provides a way for the function to perform some initialisation then take input from the calling code and then carry on processing if required.
Or put another way, it's similar to a function taking an anonymous function as an argument and then calling it in javascript.
Solution 5
I wanted to sort of add why you would do things that way to the already great answers.
No idea what language you are coming from, but assuming it is a static language, this sort of thing will look familiar. This is how you read a file in java
public class FileInput {
public static void main(String[] args) {
File file = new File("C:\\MyFile.txt");
FileInputStream fis = null;
BufferedInputStream bis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream(file);
// Here BufferedInputStream is added for fast reading.
bis = new BufferedInputStream(fis);
dis = new DataInputStream(bis);
// dis.available() returns 0 if the file does not have more lines.
while (dis.available() != 0) {
// this statement reads the line from the file and print it to
// the console.
System.out.println(dis.readLine());
}
// dispose all the resources after using them.
fis.close();
bis.close();
dis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ignoring the whole stream chaining thing, The idea is this
- Initialize resource that needs to be cleaned up
- use resource
- make sure to clean it up
This is how you do it in ruby
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Wildly different. Breaking this one down
- tell the File class how to initialize the resource
- tell the file class what to do with it
- laugh at the java guys who are still typing ;-)
Here, instead of handling step one and two, you basically delegate that off into another class. As you can see, that dramatically brings down the amount of code you have to write, which makes things easier to read, and reduces the chances of things like memory leaks, or file locks not getting cleared.
Now, its not like you can't do something similar in java, in fact, people have been doing it for decades now. It's called the Strategy pattern. The difference is that without blocks, for something simple like the file example, strategy becomes overkill due to the amount of classes and methods you need to write. With blocks, it is such a simple and elegant way of doing it, that it doesn't make any sense NOT to structure your code that way.
This isn't the only way blocks are used, but the others (like the Builder pattern, which you can see in the form_for api in rails) are similar enough that it should be obvious whats going on once you wrap your head around this. When you see blocks, its usually safe to assume that the method call is what you want to do, and the block is describing how you want to do it.
Related videos on Youtube
Comments
-
Matt Elhotiby almost 3 years
I am trying to understand blocks and
yield
and how they work in Ruby.How is
yield
used? Many of the Rails applications I've looked at useyield
in a weird way.Can someone explain to me or show me where to go to understand them?
-
Ken Bloom almost 14 yearsYou might be interested in the answer to Ruby’s yield feature in relation to computer science. Though it's a somewhat different question than yours, it may shed some light on the matter.
-
-
maerics almost 14 yearsGreat description in that article, it took me months to figure that all out on my own =)
-
theIV almost 14 yearsI agree. I don't think I knew half of the stuff explained until I read it.
-
Paritosh Piplewar over 11 yearshow it prints
racsO
ifthe_name = ""
-
OscarRyz over 11 yearsSorry, the name is an instance variable initialized with
"Oscar"
( is not very clear in the answer ) -
f.ardelian over 11 yearsWhat about code like this?
person.do_with_name {|string| yield string, something_else }
-
yitznewton about 11 yearsSo in Javascripty terms, it's a standardized way of passing a callback to a given method, and calling it. Thanks for the explanation!
-
Michael Hampton over 9 yearsLet's simplify that a bit:
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
and laugh even harder at the Java guys. -
akostadinov about 9 years@MichaelHampton, laugh after you read a file a couple of gigabytes long.
-
Michael Hampton about 9 years@akostadinov No... that makes me want to cry!
-
LPing almost 9 yearsGood to know different ways to trigger a block.
-
Nic almost 9 years@MichaelHampton Or, better yet:
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(plus no memory issues) -
klenwell almost 8 yearsThe updated link is 404 now, too. Here's the Wayback Machine link.
-
theIV almost 8 years@klenwell thanks for the heads up, I've updated the link again.
-
Roman Bulgakov almost 7 yearsIn a more general way - a block is a ruby "enhanced" syntax sugar for Strategy pattern. because typical usage is to provide a code to do something in context of other operation. But ruby enhancements open a way to such cool things as writing DSLs using block to pass context around
-
Ulysse BN almost 6 yearsOk, but why ? There are plenty of reasons, such as the one
Logger
has to not perform some task if the user don't need to. You should explain yours though... -
makstaks about 4 yearsThanks @OscarRyz, I added a repl.it to help illustrate your great examples: repl.it/@makstaks/blocksandyieldsrubyexample
-
OscarRyz about 4 years@hmak awesome, I added the link to the answer
-
Eric almost 4 yearsThis is the (only) answer that really makes me understand what is block and yield, and how to use them.
-
Nick M almost 3 yearsCan you fix the link please, it's 404'ing
-
OscarRyz almost 3 years@NickM I removed the broken link and put all the examples in a copy/paste ready form at the bottom of the answer
-
Nick M almost 3 yearsCheers mate, much appreciated!