class Proc

A Proc object 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 is an essential concept in Ruby and a core of its functional programming features.

square = Proc.new {|x| x**2 }

square.call(3)  #=> 9
# shorthands:
square.(3)      #=> 9
square[3]       #=> 9

Proc objects are closures, meaning they remember and can use the entire context in which they were created.

def gen_times(factor)
  Proc.new {|n| n*factor } # remembers the value of factor at the moment of creation
end

times3 = gen_times(3)
times5 = gen_times(5)

times3.call(12)               #=> 36
times5.call(5)                #=> 25
times3.call(times5.call(4))   #=> 60

Creation

There are several methods to create a Proc

Lambda and non-lambda semantics

Procs are coming in two flavors: lambda and non-lambda (regular procs). Differences are:

Examples:

# +return+ in non-lambda proc, +b+, exits +m2+.
# (The block +{ return }+ is given for +m1+ and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { return }; $a << :m2 end; m2; p $a
#=> []

# +break+ in non-lambda proc, +b+, exits +m1+.
# (The block +{ break }+ is given for +m1+ and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { break }; $a << :m2 end; m2; p $a
#=> [:m2]

# +next+ in non-lambda proc, +b+, exits the block.
# (The block +{ next }+ is given for +m1+ and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { next }; $a << :m2 end; m2; p $a
#=> [:m1, :m2]

# Using +proc+ method changes the behavior as follows because
# The block is given for +proc+ method and embraced by +m2+.
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { return }); $a << :m2 end; m2; p $a
#=> []
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { break }); $a << :m2 end; m2; p $a
# break from proc-closure (LocalJumpError)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { next }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]

# +return+, +break+ and +next+ in the stubby lambda exits the block.
# (+lambda+ method behaves same.)
# (The block is given for stubby lambda syntax and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { return }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { break }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { next }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]

p = proc {|x, y| "x=#{x}, y=#{y}" }
p.call(1, 2)      #=> "x=1, y=2"
p.call([1, 2])    #=> "x=1, y=2", array deconstructed
p.call(1, 2, 8)   #=> "x=1, y=2", extra argument discarded
p.call(1)         #=> "x=1, y=", nil substituted instead of error

l = lambda {|x, y| "x=#{x}, y=#{y}" }
l.call(1, 2)      #=> "x=1, y=2"
l.call([1, 2])    # ArgumentError: wrong number of arguments (given 1, expected 2)
l.call(1, 2, 8)   # ArgumentError: wrong number of arguments (given 3, expected 2)
l.call(1)         # ArgumentError: wrong number of arguments (given 1, expected 2)

def test_return
  -> { return 3 }.call      # just returns from lambda into method body
  proc { return 4 }.call    # returns from method
  return 5
end

test_return # => 4, return from proc

Lambdas are useful as self-sufficient functions, in particular useful as arguments to higher-order functions, behaving exactly like Ruby methods.

Procs are useful for implementing iterators:

def test
  [[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
                            #  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
end

Inside map, the block of code is treated as a regular (non-lambda) proc, which means that the internal arrays will be deconstructed to pairs of arguments, and return will exit from the method test. That would not be possible with a stricter lambda.

You can tell a lambda from a regular proc by using the lambda? instance method.

Lambda semantics is typically preserved during the proc lifetime, including &-deconstruction to a block of code:

p = proc {|x, y| x }
l = lambda {|x, y| x }
[[1, 2], [3, 4]].map(&p) #=> [1, 3]
[[1, 2], [3, 4]].map(&l) # ArgumentError: wrong number of arguments (given 1, expected 2)

The only exception is dynamic method definition: even if defined by passing a non-lambda proc, methods still have normal semantics of argument checking.

class C
  define_method(:e, &proc {})
end
C.new.e(1,2)       #=> ArgumentError
C.new.method(:e).to_proc.lambda?   #=> true

This exception ensures that methods never have unusual argument passing conventions, and makes it easy to have wrappers defining methods that behave as usual.

class C
  def self.def2(name, &body)
    define_method(name, &body)
  end

  def2(:f) {}
end
C.new.f(1,2)       #=> ArgumentError

The wrapper def2 receives body as a non-lambda proc, yet defines a method which has normal semantics.

Conversion of other objects to procs

Any object that implements the to_proc method can be converted into a proc by the & operator, and therefore can be consumed by iterators.

class Greeter
  def initialize(greeting)
    @greeting = greeting
  end

  def to_proc
    proc {|name| "#{@greeting}, #{name}!" }
  end
end

hi = Greeter.new("Hi")
hey = Greeter.new("Hey")
["Bob", "Jane"].map(&hi)    #=> ["Hi, Bob!", "Hi, Jane!"]
["Bob", "Jane"].map(&hey)   #=> ["Hey, Bob!", "Hey, Jane!"]

Of the Ruby core classes, this method is implemented by Symbol, Method, and Hash.

:to_s.to_proc.call(1)           #=> "1"
[1, 2].map(&:to_s)              #=> ["1", "2"]

method(:puts).to_proc.call(1)   # prints 1
[1, 2].each(&method(:puts))     # prints 1, 2

{test: 1}.to_proc.call(:test)       #=> 1
%i[test many keys].map(&{test: 1})  #=> [1, nil, nil]

Orphaned Proc

return and break in a block exit a method. If a Proc object is generated from the block and the Proc object survives until the method is returned, return and break cannot work. In such case, return and break raises LocalJumpError. A Proc object in such situation is called as orphaned Proc object.

Note that the method to exit is different for return and break. There is a situation that orphaned for break but not orphaned for return.

def m1(&b) b.call end; def m2(); m1 { return } end; m2 # ok
def m1(&b) b.call end; def m2(); m1 { break } end; m2 # ok

def m1(&b) b end; def m2(); m1 { return }.call end; m2 # ok
def m1(&b) b end; def m2(); m1 { break }.call end; m2 # LocalJumpError

def m1(&b) b end; def m2(); m1 { return } end; m2.call # LocalJumpError
def m1(&b) b end; def m2(); m1 { break } end; m2.call # LocalJumpError

Since return and break exits the block itself in lambdas, lambdas cannot be orphaned.

Anonymous block parameters

To simplify writing short blocks, Ruby provides two different types of anonymous parameters: it (single parameter) and numbered ones: _1, _2 and so on.

# Explicit parameter:
%w[test me please].each { |str| puts str.upcase } # prints TEST, ME, PLEASE
(1..5).map { |i| i**2 } # => [1, 4, 9, 16, 25]

# it:
%w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE
(1..5).map { it**2 } # => [1, 4, 9, 16, 25]

# Numbered parameter:
%w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE
(1..5).map { _1**2 } # => [1, 4, 9, 16, 25]

it

it is a name that is available inside a block when no explicit parameters defined, as shown above.

%w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE
(1..5).map { it**2 } # => [1, 4, 9, 16, 25]

it is a “soft keyword”: it is not a reserved name, and can be used as a name for methods and local variables:

it = 5 # no warnings
def it(&block) # RSpec-like API, no warnings
   # ...
end

it can be used as a local variable even in blocks that use it as an implicit parameter (though this style is obviously confusing):

[1, 2, 3].each {
  # takes a value of implicit parameter "it" and uses it to
  # define a local variable with the same name
  it = it**2
  p it
}

In a block with explicit parameters defined it usage raises an exception:

[1, 2, 3].each { |x| p it }
# syntax error found (SyntaxError)
# [1, 2, 3].each { |x| p it }
#                        ^~ `it` is not allowed when an ordinary parameter is defined

But if a local name (variable or method) is available, it would be used:

it = 5
[1, 2, 3].each { |x| p it }
# Prints 5, 5, 5

Blocks using it can be nested:

%w[test me].each { it.each_char { p it } }
# Prints "t", "e", "s", "t", "m", "e"

Blocks using it are considered to have one parameter:

p = proc { it**2 }
l = lambda { it**2 }
p.parameters     # => [[:opt, nil]]
p.arity          # => 1
l.parameters     # => [[:req]]
l.arity          # => 1

Numbered parameters

Numbered parameters are another way to name block parameters implicitly. Unlike it, numbered parameters allow to refer to several parameters in one block.

%w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE
{a: 100, b: 200}.map { "#{_1} = #{_2}" } # => "a = 100", "b = 200"

Parameter names from _1 to _9 are supported:

[10, 20, 30].zip([40, 50, 60], [70, 80, 90]).map { _1 + _2 + _3 }
# => [120, 150, 180]

Though, it is advised to resort to them wisely, probably limiting yourself to _1 and _2, and to one-line blocks.

Numbered parameters can’t be used together with explicitly named ones:

[10, 20, 30].map { |x| _1**2 }
# SyntaxError (ordinary parameter is defined)

Numbered parameters can’t be mixed with it either:

[10, 20, 30].map { _1 + it }
# SyntaxError: `it` is not allowed when a numbered parameter is already used

To avoid conflicts, naming local variables or method arguments _1, _2 and so on, causes an error.

  _1 = 'test'
# ^~ _1 is reserved for numbered parameters (SyntaxError)

Using implicit numbered parameters affects block’s arity:

p = proc { _1 + _2 }
l = lambda { _1 + _2 }
p.parameters     # => [[:opt, :_1], [:opt, :_2]]
p.arity          # => 2
l.parameters     # => [[:req, :_1], [:req, :_2]]
l.arity          # => 2

Blocks with numbered parameters can’t be nested:

%w[test me].each { _1.each_char { p _1 } }
# numbered parameter is already used in outer block (SyntaxError)
# %w[test me].each { _1.each_char { p _1 } }
#                    ^~