Blocks and Yields in Ruby

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 is nil

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 is nil

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 yields. 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 eaches 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 Procs, 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, Procs 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



Leave a reply



Submit