Ruby lambda, proc, block

Igor Guzak
6 min readFeb 16, 2020

--

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}!"
end
perform('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?
end
perform_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
end
greeting = proc { "Hello!" }
perform_proc(greeting) # => "Hello!"
perform_block(&greeting) # => "Hello!"
perform_block(&-> { "Convert lambda to block" })

Conversion of lambda to method's block argument doesn’t mean that we change its behavior, so yield or block.call will invoke the same lambda which we pass to the method as block

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}!"
end
greeting { 'John' } # => 'Hello John!'
greeting { next 'John' } # => 'Hello John!'
greeting { return 'Ups' } # => LocalJumpError (unexpected return)
greeting { break 'Bye!' } # => 'Bye!'

While break inside block works fine, you could be quite disappointed to notice that it doesn’t work in the same way for proc and produce LocalJumpError (break from proc-closure) no matter where you call it. It’s strange since block is just the same proc 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}!"
end
greeting -> { 'John' } # => 'Hello John!'
greeting -> { next 'John' } # => 'Hello John!'
greeting -> { return 'again' } # => 'Hello again!'
greeting -> { break 'one more time' } # => 'Hello one more time!'

--

--