Procs and Lambdas

marynanogtieva

Maryna Nogtieva

Posted on April 27, 2020

Procs and Lambdas

Today I was refreshing my knowledge and learning something new about proc and lambda in Ruby and wanted to share some info about it that, hopefully, might be useful for somebody else.

In the previous post we discussed that almost everything is an object in Ruby except blocks.
Block in Ruby is a very useful feature that helps to remove some repetition in the code and perform various operations on data structures dynamically.

However, sometimes(not too often) we might need to have a way to store a block of code into a variable, which is not possible with regular Blocks in Ruby:

# This will not work and an exception will be thrown
a = { puts "Hello" }  # Ruby expects a hash here between curly braces

# syntax error, unexpected keyword_do_block
b = do
  puts "Hello"
end

Enter lambdas and procs ✨

Lambda and proc are wrappers around a block that allow us to store a block of code into a variable and use it later. This variable will be an object - an instance of the Proc class.

After a proc is created it can be later passed as an argument to a method.

names = ['Anna', 'Alex', 'Hellie', 'Martin']
proc_name = Proc.new { |name| name.start_with?('A') }
selected_names = names.select(&proc_name)

In the example above we can see only two names printed out

["Anna", "Alex"]

Creating lambda and proc

Proc class has two flavors: procs and lambdas. We will discuss their differences in a moment but first let's review how we can create them. There are few ways both to create and call procs and lambdas:

proc1 = Proc.new { puts "I'm proc1" }
##<Proc:0x00007fce97948b40@(irb):117>
proc2 = proc { puts "I'm proc2" }
##<Proc:0x00007fce9793a180@(irb):118>

lambda1 = lambda { puts "I'm lambda1" }
##<Proc:0x00007fce9792a870@(irb):120 (lambda)>
lambda2 = -> { puts "I'm lambda2" }
##<Proc:0x00007fce9791bbb8@(irb):121 (lambda)>

Stabby lambda -> is a new syntax for creating lambdas and is used more often than lambda keyword.

Calling lambda and proc

There are many ways to call both lambda and proc. I personally prefer .call() and .() syntax.


proc1.call()
lambda1.call()
I'm proc1
I'm lambda1


proc1.()
lambda1.()
I'm proc1
I'm lambda1

proc1.[]
lambda1.[]
I'm proc1
I'm lambda1

proc1.===
lambda1.===
I'm proc1
I'm lambda1

Difference between lambda and proc

Both lambda and proc are similar on the surface but actually behave quite differently. They are similar in a way that they are instances of the same Proc class.

irb(main):126:0> proc1.class
Proc < Object

irb(main):127:0> lambda1.class
Proc < Object

But one should not be confused by that fact. We should take a closer look at lambda and proc:

  • proc is not strict with the number of arguments passed
number_of_students = Proc.new do |number|
 if number
  puts number
 else
  puts "no students"
 end
end

# prints 20
number_of_students.call(20)

# prints "no students"
number_of_students.call()
  • lambda can scold you in case you pass the wrong number of arguments.
number_of_students2 = ->(number) do
 if number
  puts number
 else
  puts "no students"
 end
end

# prints 10
number_of_students2.call(10)

# throws an exception: ArgumentError (wrong number of arguments (given 0, expected 1))
number_of_students2.call()
  • Keyword return has a different behavior:
  • “return” in lambda behaves like “return” in a method - you call a method from another method, get a return value, and carry on with the code below it.
  • "return" in proc behaves the same way as in a block. If a proc with return keyword is called inside a method, then code specified below proc.call() will not be executed. The method will return a value that is being returned by the proc. Note, if you declare proc with return in the top context and then pass it to a method as an argument, you will get an exception LocalJumpError: unexpected return because we are not allowed to return from top-level context.

Let's look at some examples.

  • When a lambda is declared outside of the method(passed to the method as an argument) and when another lambda declared inside a method, we will see all four lines printed out.
def my_lambda(lambda_obj)
  internal_lambda = -> { return "in internal lambda" }

  puts "before lambda"
  puts internal_lambda.call
  puts lambda_obj.call
  puts "after lambda"
end

lambda_obj = -> { return "in outside lambda" }
my_lambda(lambda_obj)

Output:

before lambda
in internal lambda
in outside lambda
after lambda
  • When a proc is declared outside the method in a top-level scope an error will be thrown.
outside_proc = Proc.new { return "From outside proc" }

def my_proc(proc_obj)
  puts "before proc"
  puts proc_obj.call()
  puts "after_proc"
end

my_proc(outside_proc)

Output:

before proc
LocalJumpError: unexpected return
    from (irb):98:in `block in irb_binding'
    from (irb):102:in `my_proc'
  • When proc is declared inside the method, it will be binded to the method scope, and lines that come after calling proc will not be printed out.
def my_proc
  proc_obj = Proc.new { return "From inside proc" }

  puts "before proc"
  puts proc_obj.call()

  # this will not be printed out
  puts "after_proc"
end

my_proc

Output:

before proc
"From inside proc"

Ruby has functional programming features?

Ruby is one of the purest object-oriented languages but it is spiced up with functional programming flavors. I have some experience with JavaScript and my examples will reference this language for comparison purposes.

  • Closures. In JavaScript closure allows the inner function to have access to the lexical scope of the outer function. Procs in Ruby are also bonded to the variable and have access to the context/scope where that variable was declared. Let's see an example:
salary = 1000
bonus  = 500

salary_and_bonus = Proc.new { puts "#{salary + bonus}" }

def print_salary_with_bonus(my_proc)
  bonus = 700
  my_proc.call
end

Will the result of calling #print_salary_with_bonus method be a 1500 or 1700 ?

irb(main):116:0> print_salary_with_bonus(salary_and_bonus)
1500

Because we declared our salary_and_bonus proc in the outer scope it had access (remembered) to everything else declared in the same context. Therefore, our output printed 1500.

  • Composition. >> operator in Ruby. A composition is an assembly of multiple functions combined together into one function to perform multiple operations on one piece of data. Basically, composition allows us to combine two or more functions into one function.

Proc#>> was introduced in Ruby 2.6 and it allows us to perform some fun tasks. Let's imagine that we went to an online store and decided to purchase one item from there.
Here is how we would calculate its final price by using a composition:

WHOLESALE_PRICE = 400

base_price     = ->(price) { WHOLESALE_PRICE + price }
handling_price = ->(price) { price + price * 0.1 }
total_tax      = -> (price) { price + price * 0.13 }

calculate_final_price = base_price >> handling_price >> total_tax
final_price           = calculate_final_price.call(100)

# final price => 621.5

Interestingly, Proc#>> in ruby switches the order of a function call comparing to composition in Haskel or Math.
Usually, when we use a composition the order of data processing goes from right to left f(g(x)) - first we process x and then g.

With Proc#>> we process data from left to right. However, Proc#<< allows us to process data the same way as it's done in functional programming 🙂 . I suggest checking this blog for more details on that.

Here is a nice article to learn more about composition in Ruby in detail.

  • Function currying. If you learned any functional programming you probably encountered currying paradigm before.

With the help of currying, you can take a function with 2 arguments, call this function with one argument. This is called a partial application. Then call the partial application with the final argument to complete the function and evaluate the result.

If you are wondering how currying in ruby can be useful "Applying Higher-order Functions: Partial Application and Currying" part of this article would be a very good read to get more knowledge on that.

Let's try to take the previous example of an item's price calculation and try to use Proc#curry for that purpose:

WHOLESALE_PRICE = 400

calculate_price = ->(method, price, additional_cost) do
  price.send(method, additional_cost)
end

add = calculate_price.curry.call(:+)

base_price = add.(WHOLESALE_PRICE, 100)
# => 500
handling_price = add.(base_price, base_price * 0.1)
# => 550.0

tax = handling_price * 0.13
final_price_with_tax = add.(handling_price, tax)
# => 621.5

Thanks for reading, I hope you found this information useful 🙂

💖 💪 🙅 🚩
marynanogtieva
Maryna Nogtieva

Posted on April 27, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Parameters VS Arguments with Ruby
ruby Parameters VS Arguments with Ruby

October 23, 2024

Usage of method reduce in Ruby
ruby Usage of method reduce in Ruby

October 25, 2023

Methods in Ruby
beginners Methods in Ruby

August 21, 2023

Hashes in Ruby
beginners Hashes in Ruby

August 20, 2023