Code blocks and other members of the closure family are something near to many Rubyists’ hearts. They are extremely powerful, flexible, and, when used well, elegant. They are the cornerstones of Ruby’s functional style of programming.
In this series, we will take a look at procs, code blocks, lambdas, and closures in Ruby and explore the differences between them and how to use them.
I was afraid of all these names for a long time. But after taking a really close look at each one of them and their relationships to each other, I realized they were not that scary at all. Instead, I promise you, they are really fun subjects.
This is part 1 of a three-part series.
Part 1 covers the following four fundamentals:
- Definitions of Code Block, Proc, Lambda, and Closure
- Constructions
- Calling a Code Block/Proc/Lambda
- Passing and Returning Procs
Part 2 covers
- Scopes, Universes, and Lunch Boxes
- Differences between a Proc and a Lambda
Part 3 covers:
- Proc <> Code Block Conversion and Ampersand(&)
Here is the meat for today.
1. Definitions
- A
code block
is a block of code. Sometimes, people simply call a code block a block. - A
proc
is an object that contains a code block. It provides a way to save up a code block and execute it later. - A
lambda
is a special kind of proc (more on that later). - A
closure
is a function that: 1. can be passed around as a variable and 2. binds to the same scope in which it was created (more on that later).
To different extents, code blocks, procs, and lambdas can be seen as closures. Closure
is a higher-level concept compared to Code Block
, Proc
,and Lambda
.
Yup, it’s dead simple and straightforward.
2. Constructions
There are two different ways to construct each of them.
- Code Block:
{ }
ORdo end
- Proc:
Proc.new
ORproc
followed by a code block - Lambda:
lambda
OR->
(stabby lambda) followed by a code block - Closure: again, Code blocks, procs, and lambdas can be seen as different forms of closures.
# Code Block #1. {} Mostly used as a one-liner { puts 'Hi' } #2. do end do puts 'Hi' end # example: arguments are wrapped between || [1, 2].map {|num| num * 2} # Proc #1. Proc.new followed by a code block Proc.new { puts 'Hi' } Proc.new do puts 'Hi' end #2. proc followed by a code block proc { puts 'Hi' } proc do puts 'Hi' end # example: arguments are wrapped between || times_2 = proc { |num| num * 2 } # Lambda # 1. lambda followed by a code block lambda { puts 'Hi' } lambda do puts 'Hi' end #2. -> (stabby lambda) -> { puts 'Hi' } -> do puts 'Hi' end #example: -> takes argument outside of {} times_2 = -> (num) { num * 2 } # Only -> takes argument outside of {} ->(arg) { puts arg } # The rest takes argument in between || { |arg| puts arg } proc do |arg| puts arg end lambda { |arg| puts arg }
3. Calling a code block/proc/lambda
As we defined earlier, a proc/lambda is no more than an object containing a code block. The purpose of a proc/lambda is to save the code block and execute it later. So calling a proc/lambda is simply executing the code block it has.
How do you call a proc/lambda?
- You call it.
How do you execute a code block?
- You yield to it.1
Let’s first take a look at calling a proc/lambda.
times_2 = Proc.new { |num| num * 2 } times_2.call(3) # returns 6 times_2 = proc { |num| num * 2 } times_2.call(3) # returns 6 times_2 = lambda { |num| num * 2 } times_2.call(3) # returns 6 times_2 = -> (num) { num * 2 } times_2.call(3) # returns 6
Similar to the way a code block returns the result of its last line of code, a proc/lambda returns the result of the last of code in its code block.
Let’s take a look at yielding to a code block.
A code block can be attached to a method.
def burger puts 'top bun' yield if block_given? puts 'bottom bun' end burger do puts 'lettuce' puts 'tomato' puts 'chicken breast' end # the above code will print out # top bun # lettuce # tomato # chicken breast # bottom bun
We can also pass arguments into a code block.
# Example: passing arguments def ten_plus puts 'before yield' yield(10) puts 'after yield' end ten_plus do |num| puts(num + 100) end # the above code will print out # before yield # 110 # after yield
4. Passing and Returning Procs
Since procs are objects, it makes sense that we can pass procs as arguments to methods the same way we do with other objects (arrays, strings, ect).
# Passing a proc into a method def give_me_a_proc(pr) puts 'Before call the proc' pr.call puts 'After calling the proc' end noisy_proc = proc { puts 'Noisy' } give_me_a_proc(noisy_proc) # The above code will print: # Before call the proc # Noisy # After calling the proc
It shouldn’t be a surprise that we can also return a proc from a method.
# Returning a proc from a method def fancy_proc_maker proc { puts 'Call me fancy' } end fancy_proc = fancy_proc_maker fancy_proc.call # The above code will print: # Call me fancy
Woohoo! Now you have a sense of what code blocks, procs, lambdas, and closures are. I bet you can’t wait to share this TIL with your Ruby-buddies.
With the basics covered, next up is some fun time with Scopes, Universes, and Lunch Boxes!
Don't forget to subscribe! ?
[1]: You can also call a “block” by converting it to a proc first, which is what we will look at in part 3 of the series.
Your last example has “called” instead of “call”
Thanks!
Updated 🙂