# Understanding Ruby Blocks

> A recap on the fundamentals of Ruby blocks.

- Canonical: https://dennisokeeffe.com/blog/2022-02-14-understanding-ruby-blocks
- Published: 2022-02-14
- Updated: 2022-02-14
- Tags: ruby

If you understand the basics of [Ruby enumerables](https://www.rubyguides.com/2016/03/enumerable-methods/), then you are ready to look at the concept of [Ruby blocks](https://www.rubyguides.com/2016/02/ruby-procs-and-lambdas/) (including procs and lambdas).

Blocks in Ruby are anonymous functions that can be passed to methods (such as our enumerable method `map`).

They can be enclosed by our standard `do/end` statement or inline using brackets `{}`.

Ruby blocks can also define arguments that are passed to the block between two pipes `|`.

As denoted by the linked resource: if you have ever used the `each` method, you will know that the `do/end` statement is the most common way to define blocks and you will have used them before!

```rb
# A Ruby block using the `do/end` statement
[1,2,3].each do |num|
	puts num + 1 # => 2, 3, 4
end
```

We can inline this block to be even shorter with the bracket `{}` notation:

```rb
# Using the bracket notation
[1,2,3].each { |num| puts num + 1 } # => 2, 3, 4
```

We can spruce things up with our Ruby blocks by using them in our own methods or even storing blocks as variables.

## The `yield` keyword

`yield` is a keyword that can be called within a method to return execution back to the accompanying block. For example:

```rb
def my_method
	puts "Hello"
	yield
	puts "World"
end

my_method do
	puts "Goodbye"
end

# Prints:
# Hello
# Goodbye
# World
```

In the above example, you can see that the usage of `yield` enables us to execute the `puts "Goodbye"` statement that was defined within the `do/end` block.

We could even use `yield` to pass arguments to the block:

```rb
def my_method
	yield "Bob"
end

my_method do |name|
	puts "Hello #{name}"
end

# Prints: Hello Bob

# Same output as above
my_method { |name| puts "Hello #{name}" }

# Prints: Hello Bob
```

`yield` can also be defined multiple times within a method to denote that we wish to execute multiple blocks.

```rb
def my_method
	yield "Bob"
	yield "Sue"
end

my_method { |name| puts "Hello #{name}" }

# Prints:
# Hello Bob
# Hello Sue
```

Where could this be useful? We could combine the use of blocks with enumerators to create a method that will iterate over an array and execute a block for each element.

```rb
@players = ["Bob", "Sue", "Jim"]

def players_statement
	@players.each do |name|
		yield name
	end
end

players_statement { |name| puts "Player: #{name}" }

# Prints:
# Player: Bob
# Player: Sue
# Player: Jim
```

We could even hoist the `puts` from the block into the method definition:

```rb
@players = ["Bob", "Sue", "Jim"]

def players_statement
	@players.each do |name|
		puts yield name
	end
end

players_statement { |name| "Player: #{name}" }

# Prints:
# Player: Bob
# Player: Sue
# Player: Jim
```

What ever is yielded can be used in the initial method definition. This gives us control over how we use the return values.

## Handling undefined blocks

What happens if we try to execute a block that has not been defined?

```rb
def my_method
  yield
end

my_method
# => `my_method': no block given (yield) (LocalJumpError)
```

We have an error as the result. We can therefore use the `block_given?` method to check if a block has been defined.

```rb
def my_method
  if block_given?
    puts "block given"
	else
		puts "no block given"
  end
  puts "executed regardless"
end

my_method
# => no block given
# => executed regardless

my_method {}
# => block given
# => executed regardless
```

An example of a function that implements the `block_given?` behavior is [`#count`](https://ruby-doc.org/core-3.0.3/Enumerable.html#method-i-count).

## Lambda blocks

Lambdas in Ruby give us the capability of assigning a block to a variable.

This enables use to treat blocks as first-class citizens and use them in different methods without the overhead of repeating yourself.

We can declare lambdas in two ways:

1. Using the `lambda` keyword.
2. Using the `->` syntax. This is known as "stabby lambda" syntax and is more commonly used.

Here is an example of both in action:

```rb
lambda_one = lambda { puts "hello lambda" }
lambda_two = -> { puts "hello stabby lambda" }

lambda_one.call
# => hello lambda
lambda_two.call
# => hello stabby lambda
```

As seen above, we can use the `#call` method to execute the lambda.

## Passing arguments to lambda blocks

Depending on which syntax we use, we can pass arguments to the lambda block:

1. If we use the `lambda` keyword, we can pass arguments to the block using the `|` syntax.
2. If we use the `->` syntax, we can pass arguments to the block using the bracket `()` syntax.

As a demonstration:

```rb
print_name = lambda { |name| puts "Hello #{name}" }
print_name.call "Bob"
# => Hello Bob

print_name = -> (name) { puts "Hello #{name}" }
print_name.call "Bob"
# => Hello Bob
```

## Invoking lambda functions

For the sake of completion, this is a demonstration of how to invoke a lambda function in different ways:

```rb
my_name = -> (name) { puts "Hello #{name}" }

my_name.call("Bob")
my_name.("Bob")
my_name["Bob"]
my_name.=== "Bob"
```

In practice, it is best to stick with the `call` method. This syntax helps us to understand what is going on.

## The proc object

`proc` is an object that can store blocks and pass them around like variables.

> Note: A lambda is a type of proc object with some distinct differences. This will be covered further below.

```rb
proc_example = Proc.new { puts "Hello proc object" }

proc_example.call
# => Hello proc object
```

Instead of using `Proc.new`, we can also simply use the keyword `proc`.

```rb
proc_example = proc { puts "Hello proc object" }

proc_example.call
# => Hello proc object
```

Arguments for proc objects can be defined within pipes:

```rb
a_proc = proc { |name, age| puts "Person #{name} is age #{age}" }

a_proc.call "Bob", 23
# => Person Bob is age 23
```

## Understanding the different between lambdas and procs

There are some key differences that are paramount that you commit to memory:

1. Arguments: proc objects don't care how many arguments you pass to them. Lambdas do and will raise an `ArgumentError`.
2. Return values: procs return from the context in which it is called. Lambdas return the value of the return expression.

```rb
a_lambda = -> { return 1 }

a_lambda.call
# => 1

a_proc = Proc.new { return }

a_proc.call
# => localJumpError (unexpected return)

def my_method
  a_proc = Proc.new { return }
  puts "this line will be printed"

	# Note: does not return form top-level context,
	# therefore there is no error.
  a_proc.call
  puts "this line is never reached"
end

my_method
#=> this line will be printed
```

## Similarities between lambdas and procs

1. Default arguments: both support default arguments in the same many as Ruby methods.
2. Both proc objects and lambdas can be used as first-class citizens and therefore as arguments to a method.

An example of default arguments:

```rb
my_proc = Proc.new { |name="Bob"| puts name }

my_proc.call
# => Bob

my_lambda = ->(name="Bob") { puts name }

my_lambda.call
# => Bob
```

Passing a proc object/lambda to a method:

```rb
def my_method(arg)
  arg.call
end

my_lambda = -> { puts "lambda" }
my_proc = Proc.new { puts "proc" }

# Our defined lambda and proc being used as arguments to `my_method`.

my_method(my_lambda)
# => lambda

my_method(my_proc)
# => proc
```

## Capturing blocks

Blocks call also be referenced within a method so that we may do something with it.

```rb
def my_method(&block)
	block.call
end

my_method { puts "Hello" }
# => Hello
```

We are creating an explicit block when we define the argument `&block`. Implicit blocks are created when we don't define the argument.

An example of an implicit block:

```rb
def my_method
	yield
end

my_method { puts "Hello" }
# => Hello
```

It is worth notice that we can still use `yield` with an explicit block, although it is frowned upon.

The capturing syntax `&` works as Ruby will call a method `#to_proc` on whatever is assigned to the variable, so our explicit block becomes a `proc` object, hence why we can invoke `call`.

An example given of where the `&` is used in the wild:

```rb
arr = ["1", "2", "3"]

arr.map(&:to_i)
# => [1, 2, 3]
```

We to the symbol `:to_i` into a proc object that is called on each argument mapped over in the provided example.

## Using the `&` the other way around

We can use `&` on a proc object to convert it into a block.

```rb
def my_method
  yield
end

my_proc = Proc.new { puts "Hello" }

my_method(&my_proc)
# => Hello
```

If you just used `my_method(my_proc)`, it would result in an error.

It will also result in an error if the defined method (i.e. `my_method` in our case) is expected an argument.

## Built-in currying with lambdas

The final thing I wanted to touch on was the capability to curry a lambda.

```rb
add = -> (x, y) { x + y }

add_two = add.curry.call(2)
add_two.call(3)
# => 5
```

This means that we can simplify and reuse more functional approaches to calling re-used utility functions.

## Resources and further reading

- [Ruby Enumerable Methods](https://www.rubyguides.com/2016/03/enumerable-methods/)
- [Ruby Blocks](https://www.rubyguides.com/2016/02/ruby-procs-and-lambdas/)
- [Ruby lambda keyword](https://ruby-doc.org/core-3.1.0/Kernel.html#lambda-method)
- [The Odin Project: Blocks](https://www.theodinproject.com/paths/full-stack-ruby-on-rails/courses/ruby-programming/lessons/blocks#capturing-blocks)
- [Using Lambdas in Ruby](https://www.honeybadger.io/blog/using-lambdas-in-ruby/)
