Ruby: Blocks, Procs & Lambdas

When you start out in ruby
, your experience might be similar to mine, everything was going swimmingly strings
, integers
and booleans
were all ✅. Until I ran into the forest of blocks and iterators, do
and end
things that well let’s face it, make up the majority of what the Ruby language is, and allows for the passing around of objects. Event 12 months on into my software engineering journey I struggle with the concepts of yield
and blocky kinda of things..
So just like your first night on a Minecraft Hard-Core server you can either:
- Dig a hole, jump in, wait & hope the monsters are gone in the morning
- Be pro-active and chop down a tree, make a 🪓 and go hunting 🧟
what’s in a block for you ? 📦
blocks
are handy bits of code that can be written and executed later, they require the do
and end
syntax like below.
[1,2,3].each do |n|
puts "printing number #{n}"
end
# printing number 1
# printing number 2
# printing number 3
Now let’s look into the Array#each under the hood to see just what is happening with the block
that is getting passed to the method.
def each
i = 0
while i < size
yield at(i)
i += 1
end
end
Whooaa.. there is plenty going on inside .each
:
- we have a
while
loop stepping through the indexed objects in the Array ( in this case the numbers 1, 2, & 3 ) - this is the part we are most interested in
yield
keyword
yield to me
So as the .each
method steps through the Array it has to yield
to the block
at each number, and perform the action / code inside the block
that proceed it. In the case of the above code samples it results of the printing of the numbers inside the array to the console.
# printing number 1
# printing number 2
# printing number 3
you can ignore your block
Just because you place a block
after a method, doesn’t mean that the method will do anything with it, ( well not always but we’ll get to explicitly later). You can also explore block_given?
.
(1..10).to_a.reverse do
puts "print my block 10 times"
end
# reverse says no!
#=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
your method might block you from ignoring your block
Using the &block
syntax will force you to pass a block to the method otherwise 🧨 will happen ( well an ArgumentError
).
def explicit_block(&block)
block.call # same as yield
end
# no block
explicit_block("don't block me")
# 🧨 wrong number of arguments (given 1, expected 0)
# with block
explicit_block { puts "don't block me" }
#=> "don't block me"
Lambdas 🐑
I first ran into lambda
syntax in scopes inside Rails models
# scopes
scope :recipes, -> { where(favoritable_type: 'Recipe') }
Just like methods, lambdas
won’t run unless you make them, and you need to use the .call
method for that. You can also store them in variables like below.
cats_on_a_mat = -> { puts "Many cats on a mat.." }
cats_on_a_mat.call
#=> "Many cats on a mat.."
lambdas
will also get upset if you pass them different number of arguments to what they are expecting.
upcase_my_2_cats = ->(cat_1, cat_2) { puts "#{cat_1.upcase} & #{cat_2.upcase}" }
upcase_my_2_cats.call("flossie","sydney")
#=> "FLOSSIE & SYDNEY"
upcase_my_2_cats.call("fido")
# 🧨 wrong number of arguments (given 1, expected 0)
Procs 📜
Procs are the closest thing Ruby has to first-class
functions, let’s try and understand this, because I sure am trying to..
procs
are not so sensitive aboutargs
upcase_my_2_cats = Proc.new { | cat_1, cat_2 | puts "will this print in a Proc ?" }
upcase_my_2_cats.call()
#=> "will this print in a Proc ?"
procs
return
from the current context, alambda
willreturn
from itsself
like a “normal method”.. let’s try and dig into that.
my_lambda = -> { return 1 }
puts "Lambda result: #{my_lambda.call}"
#=> Lambda result: 1
my_proc = Proc.new { return 1 }
puts "Proc result: #{my_proc.call}"
#🧨 => unexpected return (LocalJumpError)
The
proc
fails. The reason is that you can’t return from the top-level context. It can notreturn
from its.self
What it can do..
def call_proc
puts "Before proc"
my_proc = Proc.new { return "cat" }
my_proc.call
puts "After proc"
end
p call_proc
# => Before proc
# => cat
# Prints "Before proc" but not "After proc"
So what’s happening here ? The proc
is causing the method to return
and no other lines are executed. You could look at above and start to get the feeling that procs
have a little bit of different behaviour than methods
and lambdas
.
Let’s unpack this with a light dig into the topic of closure
def call_proc(my_proc)
count = 500
my_proc.call
end
# the context
count = 1
my_proc = Proc.new { puts count }
# method call
p call_proc(my_proc)
# What does this print?
# 500 || 1 ?
In the end, it would seem the most logical conclusion is to print 500
, but due to the “closure” effect, it will print 1
.
This happens because the proc
uses the value of count
from the location / context of where it was defined, which is outside the method definition.
yes, that’s a lot to take in..
Well, hopefully now your ready to jump in and face your block
fears head on & have learned a bits about procs
and lambdas
.

references:
- app signal
- app signal #2
- code quizzes - intersting article on ruby closure
- ruby guides
bits that didn’t fit
def do_something_with_block
return "No block given" unless block_given?
yield
end
# This prevents the error if someone calls your method without a block.