DEV Community

Maryna Nogtieva
Maryna Nogtieva

Posted on

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 🙂

Top comments (4)

Collapse
 
ihtay profile image
Yathi • Edited

Absolutely love this! As a fan of functional programming, I am glad to see that Ruby allows for these functional concepts. I knew the basics of lambdas in ruby but I did not know that you could even do composition and partial applications over them! I can’t wait to try these out once we upgrade to Ruby 2.6. Thanks a lot.

It also made me realize that >> which is similar to pipes in Elixir or piping in general is just reverse composition. Mind blown! :)

Collapse
 
bizzibody profile image
Ian bradbury

Awesome post. I tend to run away from Lambdas and procs. Not anymore! Time to look at them with fresh eyes.

Collapse
 
dividedbynil profile image
Kane Ong

Nice post!
Have you rewritten some OOP code with the functional programming feature in Ruby? It will be interesting to see the differences!

Collapse
 
marynanogtieva profile image
Maryna Nogtieva

I have not yet, but that's an interesting idea!