Source: unsplash.com

In this article, we’re going to explore the following topics:

the yield keyword

keyword blocks & yield arguments

arguments return values

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.

Before to start

I’m thrilled to share with you our latest project: Fun Facts about Ruby — Volume 1

Please feel free to spread the word and share this link! 🙏

Thank you for your time!

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 yield s in the method

produces

one yield

multiple yields

multiple yields

The code inside the block will “replace” 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 the method requires a block. Otherwise, a LocalJumpError is raised

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

Here yield is process only if a block is passed during method call.

Arguments

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

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

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

The hello_world variable contains the "Hello World!" returned by the block.

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 returns 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 array variable.

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

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

$> 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 behavior 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 calling 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 yield(elem) in ary variable.

4- we return ary .

Now, what happens if we forget to 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

$> 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 calling array.

Voilà !