My Ruby 💌 newsletter 💌 is available here ! Feel free to subscribe ! 🚀

The yield keyword

In this article we’re going to describe the yield keyword, blocks, yield arguments, return values and an implementation of the Enumerable#map method.

The yield keyword — in association with a block — allows to pass a set of additional instructions during a method invocation.

This mechanism allows you to customise a method depending on your needs.

What’s a block ?

A block is part of the Ruby method syntax.

This means that when a block is recognised by the Ruby parser then it’ll be associated to the invoked method and literally replaces the yields in the method

def one_yield
yield
end
def multiple_yields
yield
yield
end
$> one_yield { puts "one yield" }
one yield
=> nil
$> multiple_yields { puts "multiple yields" }
multiple yields
multiple yields
=> nil

The code inside the block will literally replace the yield keyword in the method definition.

Now, what happens if we don’t pass a block during method invocation for a method that includes a yield ?

block_given?

When yield is called in a method then the method requires a block. Otherwise, a LocalJumpError is raised.

def yield_without_block
yield
end
$> yield_without_block
LocalJumpError (no block given (yield))

So, how to make the block optional ? The answer is by using the Kernel#block_given? method

def optional_block
yield if block_given?
end
$> optional_block
=> nil
$> optional_block { puts 'optional block' }
optional block
=> nil

Arguments

yield can take a list of arguments that will be available for the block

def yield_with_arguments
hello = 'Hello'
world = 'World!'
yield(hello, world)
end
$> yield_with_arguments { |hello, world| puts "#{hello} #{world}" }
Hello World!
=> nil

Here, the block takes the arguments |hello, world| that are provided by the yield inside the yield_with_arguments method.

Return value

It’s possible to get the return value of a block by simply assigning the return value of a yield to a variable

def yield_with_return_value
hello_world = yield
puts hello_world
end
$> yield_with_return_value { "Hello World!" }
Hello World!
=> nil

The hello_world variable get the "Hello World!" returned by the block and display it by using puts.

Array#my_map

Now that we’ve made an overview of yield and blocks, let’s try to recap the important notions by implementing an example.

The Enumerable#map method allows you to iterate over a list of objects and manipulate each of them. Then it’ll return a new list that contains all the manipulated objects.

$> array = [1, 2, 3]
=> [1, 2, 3]
$> array.map {|n| n + 2}
=> [3, 4, 5]
$> array
=> [1, 2, 3]

In the above example, we assign [1, 2, 3] to the array variable.

Then we call the array.map {|n| n + 2} that adds 2 to each element of the array. a new array that contains [3, 4, 5] is returned by the map method.

Finally, we see that the initial array didn’t change.

Now, let’s try to implement our own version of Enumerable#map directly on the Array class

class Array
def my_map
ary = []
self.each do |elem|
ary << yield(elem)
end
ary
end
end
$> array = [1, 2, 3]
=> [1, 2, 3]
$> array.my_map {|n| n + 2}
=> [3, 4, 5]
$> array
=> [1, 2, 3]

Our custom method has the same behaviour as the Enumerable#map method.

That’s great ! Now let’s dive in the code.

1- we create a temporary Array named ary. This variable will contain the return value of our method.

2- we iterate over the receiver Array by using self.each.

3- for each element, we call yield(elem) — where elem is the current element of the iteration. Then, we store the return value of the yield(elem) in the ary variable.

4- we return ary.

Now, what happens if we don’t pass a block to our Array#my_map method?

$> array.my_map
LocalJumpError (no block given (yield))

As you probably expected, an error is raised.

But fortunately, we’ve already fixed this issue in the previous sections.

So let’s reuse the Kernel#block_given? method to enhance our Array#my_map method

class Array
def my_map
return self.dup unless block_given?
    ary = []
self.each do |elem|
ary << yield(elem)
end
ary
end
end
$> array.my_map
=> [1, 2, 3]

If there is no block passed to the method then we force the method to return a copy of the receiver array.

Voilà !


Thank you for taking the time to read this post :-)

Feel free to 👏 and share this article if it has been useful for you.

My Ruby 💌 newsletter 💌 is available here ! Feel free to subscribe ! 🚀

Here is a link to my last article: A matter of Attributes.

Like what you read? Give Mehdi Farsi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.