Blocks and yields in Ruby
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
Ruby blocks - yield {puts} vs. puts yield
In the first snippet:
- the method yields to the block
- the block prints the string
"anything"
- the method returns the return value of the blocks, which is the return value of
puts
, which isnil
In the second snippet:
- the method yields to the block
- the block returns the string
"anything"
- the method prints the return value of the block
- the method returns the value of
puts
, which isnil
Having said that, it depends on what you want to achieve with the block the methods yields to.
In the first snippet, it is more about yielding the control to the block and the block does whatever it needs to do. The return value might not be relevant, or it might be relevant as the return value of the method.
In the second snippet, the block serves as a way to calculate a value that will then be used by the method.
To illustrate the two cases, here's two examples:
def on_create
yield created_user
end
on_create do |user|
send_welcome_email(user)
end
def send_email(address)
body = yield
EmailService.deliver(address, body)
end
send_email('alice@example.com') do
"Hello Alice, ..."
end
Ruby: the yield inside of a block
The block is passed similarly to the argument of that function. This can be specified explicitly, like so:
class Test
def my_each(&block)
"abcdeabcabc".scan("a") do |x|
puts "!!! block"
yield x
# Could be replaced with: block.call(x)
end
end
end
Technically, it's exactly the same (puts
put in there for clarification), its presence is not checked the way it is usually done for arguments. Should you forget to give it a block, the function will halt on the first yield
it has to execute with exactly the same LocalJumpError
(at least, that's what I get on Rubinius). However, notice the "!!! block" in the console before it happens.
It works like that for a reason. You could check whether your function is given a block, if it is specified explicitly as above, using if block
, and then skip the yield
s. A good example of that is a content_tag
helper for Rails. Calls of this helper can be block-nested. A simplistic example:
content_tag :div do
content_tag :div
end
...to produce output like:
<div>
<div></div>
</div>
So, the block is executed "on top" (in terms of call stack) of your method. It is called each time a yield
happens as some sort of function call on a block. It's not accumulated anywhere to execute the block afterwards.
UPD:
The Enumerator
returned by many each
es is explicitly constructed by many iterators to save context of what should happen.
It could be implemented like this on my_each
:
class Test
def my_each(&block)
if block
"abcdeabcabc".scan("a") { |x| yield x }
else
Enumerator.new(self, :my_each)
end
end
end
How to yield 2 blocks in 1 method
Blocks are a lightweight way of passing a single anonymous procedure to a method. So, by definition, there cannot be two blocks passed to a method. It's not just semantically impossible, it isn't even possible syntactically.
Ruby does support first-class procedures in the form of Proc
s, however, and since those are just objects like any other object, you can pass as many of them as you want:
def by_two(n, a, proc1, proc2)
proc1.(n)
proc2.(a)
end
proc1 = proc {|x| p x * 2}
proc2 = proc {|x| x + 100}
by_two(10, 300, proc1, proc2)
# 20
# => 400
Since the introduction of lambda literals in Ruby 1.9, Proc
s are almost as syntactically lightweight as blocks, so there is not a big difference anymore:
by_two(10, 300, -> x { p x * 2 }, -> x { x + 100 })
# 20
# => 400
How does yield work with a block in Ruby?
The answer is pretty simple. You have defined your block method correctly but when you go to give it a code block you only give it one variable to hold 4 objects. Instead, try giving it a variable for each object you are yielding!
def ablock
i=1
j=2
yield(i,j,3,4)
end
ablock do |i,j,k,l|
puts i
puts j
puts k
puts l
end
If you would only like to use one variable in your code block you have to do multiple yield statements(one for each object).
def ablock
i=1
j=2
yield(i)
yield(j)
yield(3)
yield(4)
end
ablock do |i|
puts i
end
Happy coding!
Ruby: How can I properly use `yield` to pass unnamed code block to Integer#times method?
For sake of completeness on this topic, I wanted to demonstrate another technique to call the original block:
def withProc
p = Proc.new
3.times(&p)
end
withProc { print "Go" }
When Proc.new
is not given a block, it uses the block that was given to withProc
instead. Now you can call p
, and it will call the original block. You can also pass p
to other methods like times
either as a regular argument or as a block argument.
See https://medium.com/@amliving/proc-new-trick-c1df16185599 for more discussion
Ruby default block and yield
The answers are good and correct but perhaps it still do not help.
You should start with your spec:
it "reverses the string returned by the default block"
So, it's very clear what your method should do:
def reverser
# should reverse the string returned by the default block
end
Let's now see how to achieve it. Ok, it should reverse something. But what? Let's see:
string returned by the default block
This suggests that we need to execute the default block and get its returned value. Let's see what the docs say:
yield
- Called from inside a method body, yields control to the code block (if any) supplied as part of the method call. ... The value of a call to yield is the value of the executed code block.
So, it seems that your method needs to perform a yield
. It will execute a block and return the value the block returns. So, just put a yield
there.
def reverser
yield
end
If you run your spec, it will complain - you will see that the string is still not reversed. So, that's whats left for your method to do:
def reverser
yield.reverse
end
and that's it.
Ruby block taking array or multiple parameters
Ruby's block mechanics have a quirk to them, that is if you're iterating over something that contains arrays you can expand them out into different variables:
[ %w[ a b ], %w[ c d ] ].each do |a, b|
puts 'a=%s b=%s' % [ a, b ]
end
This pattern is very useful when using Hash#each
and you want to break out the key
and value
parts of the pair: each { |k,v| ... }
is very common in Ruby code.
If your block takes more than one argument and the element being iterated is an array then it switches how the arguments are interpreted. You can always force-expand:
[ %w[ a b ], %w[ c d ] ].each do |(a, b)|
puts 'a=%s b=%s' % [ a, b ]
end
That's useful for cases where things are more complex:
[ %w[ a b ], %w[ c d ] ].each_with_index do |(a, b), i|
puts 'a=%s b=%s @ %d' % [ a, b, i ]
end
Since in this case it's iterating over an array and another element that's tacked on, so each item is actually a tuple of the form %w[ a b ], 0
internally, which will be converted to an array if your block only accepts one argument.
This is much the same principle you can use when defining variables:
a, b = %w[ a b ]
a
# => 'a'
b
# => 'b'
That actually assigns independent values to a
and b
. Contrast with:
a, b = [ %w[ a b ] ]
a
# => [ 'a', 'b' ]
b
# => nil
In which context are blocks executed that are passed to a method in ruby?
- Local variables follow lexical scope.
- Block variables have scope within the block.
- Global variables have access globally.
- Everything else depends on the method that takes the block. For example,
instance_eval
evaluates the block within the context of the receiver.class_eval
evaluates the block within the context of the class of the receiver.
Related Topics
Understanding the Rails Authenticity Token
How to Pick Randomly from an Array
Connecting Rails 3.1 With Multiple Databases
Continuously Read from Stdout of External Process in Ruby
What Does 'Monkey Patching' Exactly Mean in Ruby
Rescue_From Actioncontroller::Routingerror in Rails 4
&:Views_Count' in 'Post.Published.Collect(&:Views_Count)'
Method to Parse HTML Document in Ruby
How to Solve "/Usr/Bin/Env: Ruby_Executable_Hooks: No Such File or Directory"
How to Call Methods Dynamically Based on Their Name
Combining Implicit Wait and Explicit Wait Together Results in Unexpected Wait Times
How to Find and Return a Duplicate Value in Array
Tzinfo::Datasourcenotfound Error Starting Rails V4.1.0 Server on Windows
Why Does Ruby Have Both Private and Protected Methods
How to Validate a Date in Rails