The following are some of notes about Ruby’s proc and lambda:

  • Function composition in mathmetics
  • Blocks (of code) in Ruby (normal blocks, procs & lambdas)

Function Composition in Mathmetics

In Ruby, a composite proc/lambda is an example of function composition. On Wikipedia, the definition goes by the following:

In mathematics, function composition is an operation that takes two functions f and g and produces a function h such that h(x) = g(f(x)).

# Ruby's lambda equivalent of
# h(x) = g(f(x)) = (g ∘ f)(x)
f = ->(x) { x + 1 }
g = ->(x) { puts x }
h = ->(x) { g.(f.(x)) }

h.(1) # prints 2

I see the above as if someone were “hitting combo” with the two functions, f and g. Function f gets executed first, then function g carries on and process function f’s output. And the order goes from inside out and on.

Blocks in Ruby

def x
 # "yield" yields block attached to method "x", if any
 yield
end

# Make block 2 ways
# Note there's slight difference in precedence of code execution
# 1. With curly braces
foos.each { |foo| puts foo }

# 2. With "do...end"
bars.each do |bar|
  puts bar
end

A block, or closure in other programming languages, can NOT “survive” on its own in Ruby. It has to be attached to an object, i.e., variables or methods, or in the form of a proc/lambda.

{ puts 1 } # gets "SyntaxError"
do puts 1 end # gets "SyntaxError"

Do you know there is difference between using { ... } and do ... end block? Check out my blog post Difference between “{}” & “do…end” in Ruby!

Proc and Lambda

  • Definition by ruby-doc.org

    A procedure object, or proc, is an encapsulation of a block (of code), which can be stored in a local variable, passed to a method or another proc, and can be called.

Proc

# Use the Proc class constructor
double = Proc.new { |number| number * 2 }

# Use the Kernel#proc method as a shorthand
double = proc { |number| number * 2 }

# Receive a block as an argument (note the &)
def make_proc(&block)
  block
end

double = make_proc { |number| number * 2 }

# Use Proc.new to capture a block passed to a method without an
# explicit block argument
def make_proc
  Proc.new
end

double = make_proc { |number| number * 2 }
double.call(2) # => 4
double.(2)     # => 4
double[2]      # => 4
double === 2   # => 4

Lambda - Proc with Flavor

# Use Kernel#lambda
double = lambda { |number| number * 2 }

# Use the Lambda literal syntax (more elegant)
double = ->(number) { number * 2 }
  • Ways to call a lambda

    Same as proc’s

Lambda vs. Proc

Summarized from Lambda and Non-Lambda Semantics.

Scenario Proc Lambda
a.) When there is a return on the inside… Jumps out and end the embracing function (function that called the proc) ONLY jumps out of the block
b.) When being fed on extra args… NO ERROR raised ArgumentError
c.) When there is not enough args… Missing args will be substituted with nil ArgumentError
  If the last arg entered is an array, it will be broken down to fit the missing args  

Example

Rearranged from A Guide to Function Composition in Ruby.

# a.)
proc = proc { return }
lambda = -> { return }

def call_proc_return
  proc.call
  "This will not be reached"
end

def call_lambda_return
  lambda.call
  "This will be reached"
end

call_proc_return # prints nil
call_lambda_return # prints "This will be reached"
# b.)
proc = proc { |x, y| "#{x} and #{y}" }
lambda = ->(x, y) { "#{x} and #{y}" }

proc.(1, 2, 3) # prints "1 and 2"
lambda.(1, 2, 3) # ArgumentError (wrong number of arguments (given 3, expected 2))
# c.)
proc = proc { |x, y| "#{x} and #{y}" }
lambda = ->(x, y) { "#{x} and #{y}" }

# note: some args below are a set of array,
# which is only ONE arg
proc.(1) # prints "1 and "
proc.([1, 2, 3]) # prints "1 and 2"
lambda.([1, 2, 3]) # ArgumentError (wrong number of arguments (given 1, expected 2))

References