The concept of Block
, Proc
and Lambda
first came into my life when I started to learn Ruby at Codecademy As my first programming language is Javascript which doesn't have any of these concepts, it's quite confusing while memorising them.
That's why here on day 4, I decided to explain these 3 concepts as clearly as I could.
Block
In Ruby, the word block
means the code between two curly braces {}
or do-end
.
Let's take the each
method as an example. You can see the code works in both ways.
array = [1, 2, 3]
# Using {}
array.each { |num| puts num }
# Using do-end
array.each do |num|
puts num
end
We pass a block
into each
method as an argument. And each
will pass every item into the supplied block to execute puts num
.
Proc
What is a proc exactly? We can check the following definition.
A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called
With proc, we can save some codes in a variable and then later call it or turn it into a block. It creates flexibility when we want to save our blocks separately.
Let's have some examples here.
Turning Proc into Block
We know the method each
can take a block. After the proc puts_num
is created, we have to turn it into a block before passing it to each
. Using &
operator is how we do it.
array = [1, 2, 3]
# Turning a proc into a block by using '&'
puts_num = Proc.new { |num| puts num }
array.each &puts_num
Call()
We can also call the proc directly by using call()
. Let's look at the following example.
plus_one = Proc.new { |num| num + 1 }
plus_two = Proc.new { |num| num + 2 }
plus_one.call(1) #=> 2
plus_two.call(1) #=> 3
# shorthands:
plus_one.(1) #=> 2
plus_one[1] #=> 2
Passing into function
The most interesting thing is that we can use the same &
operator to turn a proc back into a block.
In the following example, we create a function called puts_num_twice
. This function will iterate the array twice and accept an array and also a block.
Inside the function, the first iteration will use call()
method on a proc proc
transformed from &proc
. The second one will transform the proc proc
back into a block and pass it to each
method.
def puts_num_twice (array, &proc) # this '&' turns the block into a proc
# Iterate 1 : Pass an actual block to `each` and call the proc `block` indside it
array.each { |el| proc.call(el) }
# Iterate 2: This `&` turns the proc back into block and pass it to `each`
array.each &proc
end
# We pass an array and a block into the function
puts_num_twice([1,2,3]) do |num|
puts num
end
Arguments in proc
proc
has no restriction on the number of arguments. Even though you create a proc to accept 2 arguments, you can pass only one to it and it will execute without any error message.
plus_one = Proc.new { |num, num2| num + 1 }
# These codes work fine
plus_one.call(1, 2)
plus_one.call(1)
Don't put 'return' in proc
When you put a return
in a proc inside a function, this will terminate the whole function at that line.
def test_return
proc { return 4 }.call # returns from method
return 5 # this won't be executed
end
test_return #=> 4
Lambda
A lambda function is a general software concept, not specific to Ruby. They are available in many programming languages. A lambda function encapsulates control flow, parameters and local variables into a single package assigned to a variable or used inline.
How to create and call Lambda function?
With Ruby, the lambda
keyword is used to create a lambda function. It requires a block and can define zero or more parameters. You call the resulting lambda function by using the call
method.
my_lambda = lambda { puts "hello" }
my_lambda.call #=> hello
my_lambda.() #=> hello
my_lambda.[] #=> hello
my_lambda.=== #=> hello
You can also create a lambda with the literal lambda operator, which looks like this ->
and can have zero or more arguments.
my_lambda = -> { puts "hello" }
my_lambda_with_args = -> (v) { puts "hello "+v }
my_lambda.call #=> hello
my_lambda_with_args.call("newman") #=> hello newman
The differences between Proc and Lambda
It is really similar to proc
except that lambda
:
is strict on the number of arguments
acts differently when there is
return
inside it
l = lambda {|x, y| "x=#{x}, y=#{y}" }
l.call(1, 2) #=> "x=1, y=2"
l.call([1, 2]) # ArgumentError: wrong number of arguments (given 1, expected 2)
l.call(1, 2, 8) # ArgumentError: wrong number of arguments (given 3, expected 2)
l.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
def test_return
-> { return 3 }.call # just returns from lambda into method body
return 5 # this will be executed
end
test_return # => 5