I. Anonymous Functions: The Proc Class

One of the significant Callable Objects in Ruby is Proc objects and Lambdas. Proc objects inherently contain sequences of code lines and can be initialized, stored, or passed with arguments, and when needed, executed using the call method.

  • Understanding Proc objects involves grasping the following:
    • Creating and using procs.
    • How procs utilize arguments and pass variables.
    • The concept of closure in procs.
    • Similarities and differences between procs and code blocks.
    • Differences between Proc and Lambda.

Let’s start by creating an instance of Proc object using the statement: Proc.new

proc_object = Proc.new { puts "Inside a Proc's block" }
 => #<Proc:0x00000002839f58@(irb):7>
proc_object.call
Inside a Proc's block
  • The proc method

proc is a method similar to Proc.new; both create Proc objects and return the same result.

Example:

proc_object_1 = proc { puts "Hi!" }
 => #<Proc:0x00000002839f58@(irb):7>
proc_object_1.call
Hi!
proc_object_1.class
Proc
>
proc_object_2 = Proc.new { puts "Hi!" }
 => #<Proc:0x00000002853408@(irb):10>
proc_object_2.call
Hi!
proc_object_2.class
Proc

II. How are Proc and Block different?

When creating a Proc object, we always need to provide a code block, but not every code block is a Proc or serves a specific Proc.

Example: [1, 2, 3].each { |x| puts x * 10 }

The above code block does not create a Proc object.

Digging deeper, a method can take a block and convert that block into a Proc object using a special syntax &.

We define a method with a block as an argument. In Ruby, it’s called Capture the block - USING PROCS FOR BLOCKS

Example:

def capture_block(&block)
 puts "convert block into proc and call"
 block.call
 puts "block class"
 block.class
end
=> :capture_block

capture_block { puts "I'm a Proc or Block" }
convert block into proc and call
I'm a Proc or Block
block class
=> Proc

It’s important to understand what & is used for. In capture_block(&block), the & has two purposes:

  1. It converts the block into a proc object by triggering the to_proc method on the block.
  2. It marks that block as a proc object and can be executed using the call method inside the capture_block method.

We cannot call capture_block like capture_block(p) or capture_block(p.to_proc) with p being a Proc because in these cases, Ruby interprets it as a regular parameter, and you cannot trigger the proc call inside the method.

Additionally, a Proc can also be called within a code block using &.

Example:

proc = Proc.new {|x| puts x.upcase }
%w{ ruby and coffee }.each(&proc)
RUBY
AND
COFFEE
=> ["ruby", "and", "coffee"]

III. Lambda and the differences from proc

Similar to Proc.new, the lambda method also creates a Proc object.

lam = lambda { puts "A lambda!" }
=> #<Proc:0x441bdc@(irb):13 (lambda)>
lam.call
A lambda!

Lambda and proc differ in three points:

  • Lambda is explicitly initialized. Where Ruby implicitly initializes Proc objects, those created are regular procs, not lambdas. For example, in the case mentioned earlier with def capture_block(&block), the block in the capture_block method is implicitly converted into a Proc object; this block is not a lambda because a lambda must be explicitly initialized, not implicitly created.

  • Lambda and proc differ in how they handle the return method. If a return statement is executed inside a lambda, it will return out of that lambda to the calling context. Whereas, if a return is inside a proc, it will return out of the method. The following example illustrates this: the output only prints “Still here!” because the proc returns out of the method.

def return_test
  l = lambda { return }
  l.call
  puts "Still here!"
  p = Proc.new { return }
  p.call
  puts "You won't see this message!"
end

return_test

Still here!
 => nil
  • Lastly, lambda won’t execute if passed the wrong number of arguments.
lam = lambda {|x| p x }
=> #<Proc:0x42ee9c@(irb):21 (lambda)>

lam.call(1)
1
=> 1

lam.call
ArgumentError: wrong number of arguments (0 for 1)

lam.call(1,2,3)
ArgumentError: wrong number of arguments (3 for 1)

proc = proc {|x| p x }
=> #<Proc:0x42ee9c@(irb):21 (lambda)>

proc.call(1)
1
=> 1

proc.call
nil
=> nil

proc.call(1,2,3)
1
=> 1

In summary, lambda follows stricter logical reasoning. Meanwhile, proc is more lenient.