Ruby lambda, proc, block
If you are not familiar with Ruby then you could be quite scared by multiple articles about lambda
, proc
, block
and different styles of how they could be defined and used. If you are familiar with them you may already know that there is nothing special but something still could be missed, so I will try to make it cleaner and fill some gaps.
How we define our ‘proc’ and ‘lambda’
my_proc = Proc.new { p 'Hello!' } # => #<Proc:0x000055...>
my_proc = proc { p 'Hello!' } # => #<Proc:0x000055...>my_lambda = lambda { p 'Hello!' } # => #<Proc:0x000055... (lambda)>
my_lambda = -> { p 'Hello!' } # => #<Proc:0x000055... (lambda)>
You could replace {}
with do end
and have the same results but with a pretty different look and if you will mix these styles with no reasons then it will be just harder to read and one more way to frighten newcomers. So, if you have multiline definition then do end
could be better than {}
, while for inline definitions {}
is more appropriate, BTW there is no relation between lambda
and {}
as between Proc
and do end
.
In which way to construct — Proc.new
is just an explicit way of creating new proc as an instance of the Proc
class instance and almost never used on practice, so if we need proc then just use proc
keyword but even this is a rare case simply because Ruby has a more elegant way of constructing proc and it has name block
to which we will return later. What about lambda
keyword it’s mostly used when we have a multiline definition with do end
, while for inline definitions we have -> {}
, but at some project -> {}
could be used even for multiline definitions while lambda
in conjunction with do end
could be avoided at all.
What about invocation:
greeting = "Hello"
my_lambda = -> (name) { "#{greeting} #{name}!" }
my_lambda.call('John') # => 'Hello John!'greeting = "Aloha"
my_lambda.call('John') # => 'Aloha John!'
my_proc.('John') # => 'Aloha Hello!'
my_proc['John'] # => 'Aloha Hello!'
In the same way both proc
and lambda
scope outer variables, as a result of changing greeting
we changed behavior. At the same time, there are few ways of invocation, while .call
is the most common and frequently used way, some projects could follow .()
style and have:
add = -> (a) { -> (b) { a + b } }add.(1).(2) # => 3
What is the difference between ‘proc’ and ‘lambda’?
You may already noticed from the definition part that lambda
is just some kind of Proc
but there is a difference in place of receiving arguments and returning a value, while Proc
is a special kind of beast, lambda
could be simply treated as an anonymous method because it has the same behavior for incoming arguments and return
as a regular method defined with def
keyword:
greeting = -> (name) { return "Hello #{name}!" }
greeting.call('John') # => Hello John!
greeting.call() # => ArgumentError (wrong number of arguments ...
But why Proc
is something special? — Simply because it behaves in a different way in comparison with regular methods, it’s not restricted to arguments and return
not just return some value from inside of Proc
, it returns value out from scope it’s defined in:
greeting = proc { |name| return "Aloha John!" if name == 'John' }
greeting.call('Bond') # => nil
greeting.call('John') # => LocalJumpError (unexpected return)
def perform(name)
greeting = proc { |name| return "Aloha John!" if name == 'John' }
greeting.call(name)
"Hello #{name}!"
endperform('Bond') # => "Hello Bond!"
perform('John') # => "Aloha John!"
As you can see, greeting
returns value right from perform
in which it was defined. Just as a note — last value in lambda or proc will be returned as a result of .call
invocation, so use return
when it makes sense, which is not about previous example, a better example of immediate return could be:
class Array
def has?(target)
each { |item| return true if item == target }
false
end
end[1,2,3,4].has?(2) # => true
[1,2,3,4].has?(5) # => false
So, each
is a method which iterates through every item of array with invocation of code in the curly brackets for every item in array and in the place where item
equal to target
we return true
right from has?
method and only in case if nothing was matched during iteration then false
will be returned.
What about ‘block’?
In the above example, you may already notice something similar to proc
or lambda
, something in curly brackets which was passed to each
method without proc
or lambda
keywords — block
. It’s one of the things Matz is proud of, thing which is on every corner in Ruby world, no keywords needed no constructors only block of code in {}
or do end
and named block
, how it could be named in a better way?;) Ok, it’s cool, but we already have proc
and lambda
why do we need something new? — It’s not new, it’s just simple Proc
but constructed in place of method invocation in an elegant way, but since it’s not a regular argument which we are passing into the method, it needs to be handled differently:
def perform_block(&block)
block.call if block
end# ordef perform_block
yield if block_given?
endperform_block # => nil
perform_block { "Hello!" } # => "Hello!"
As you can see we need a special sign &
for argument which will receive block
and then we could simply access our variable which will contain our proc
instance, also we could use a special method yield
for invocation and block_given
to check if the block was passed. The fact that we have block
doesn’t mean that we could not create proc
manually and pass it to method explicitly, even more, we could convert proc
and lambda
to block
argument:
def perform_proc(my_proc = nil)
my_proc.call if my_proc
endgreeting = proc { "Hello!" }
perform_proc(greeting) # => "Hello!"perform_block(&greeting) # => "Hello!"
perform_block(&-> { "Convert lambda to block" })
Conversion of
lambda
to method'sblock
argument doesn’t mean that we change its behavior, soyield
orblock.call
will invoke the samelambda
which we pass to the method asblock
More ways to ‘return’
if we still need immediate return
of some value from proc
without returning from the scope in which it was defined we could use next
keyword which for lambda
works the same as return
:
class Array
def replace(target_value, new_value)
map do |item|
next new_value if item == target_value
item
end
end
end[1, 2, 3].replace(2, 'X') # => [1, "X", 3]
map
— is method which iterates over array in the same way as each
with a difference that it collects results from each iteration, so we don’t need return
from replace
method itself, we only need return value from proc
and continue iteration and next
does exactly what we need. Of course, we could avoid next
in the above example via using simple condition but in complex cases next
as part of a “guard clause” could be simpler.
So with proc
we could return value from the scope where it was defined using return
, we could simply return value from inside of proc
using next
, but what about returning value from the scope where it was invoked not defined?
def greeting(&block)
"Hello #{block.call}!"
endgreeting { 'John' } # => 'Hello John!'
greeting { next 'John' } # => 'Hello John!'
greeting { return 'Ups' } # => LocalJumpError (unexpected return)
greeting { break 'Bye!' } # => 'Bye!'
While
break
insideblock
works fine, you could be quite disappointed to notice that it doesn’t work in the same way forproc
and produceLocalJumpError (break from proc-closure)
no matter where you call it. It’s strange sinceblock
is just the sameproc
and expect to have the same behavior, so for me it sounds like language issue.
What about break
with lambda
we just have one more way to return
;)
def greeting(my_lambda)
"Hello #{my_lambda.call}!"
endgreeting -> { 'John' } # => 'Hello John!'
greeting -> { next 'John' } # => 'Hello John!'
greeting -> { return 'again' } # => 'Hello again!'
greeting -> { break 'one more time' } # => 'Hello one more time!'