Skip to content

Instantly share code, notes, and snippets.

@cflee
Created January 14, 2015 07:10
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save cflee/00c6a1b981de5bb812ee to your computer and use it in GitHub Desktop.
Save cflee/00c6a1b981de5bb812ee to your computer and use it in GitHub Desktop.
Discoveries about Ruby Blocks, Procs and Lambdas

Ruby: Blocks, Procs, Lambdas

Note that for blocks, {} and do ... end are interchangeable. For brevity, only the former will be listed here.

Version differences

Pre-1.9, lambda and proc are synonyms. Essentially the difference is between proc and block.

def method(&block) p block.class; p block.inspect; end
l = lambda { 5 }

>> method(&l)
Proc
"#<Proc:0x000000010f493030@(irb):2>"
=> nil

>> method { 5 }
Proc
"#<Proc:0x000000010f487d20@(irb):4>"
=> nil

Since 1.9, lambda is a special kind of proc, observable in inspect:

def method(&block) p block.class; p block.inspect; end
l = lambda { 5 }

>> method(&l)
Proc
"#<Proc:0x007fdb128ac3a8@(irb):2 (lambda)>"
=> "#<Proc:0x007fdb128ac3a8@(irb):2 (lambda)>"

>> method { 5 }
Proc
"#<Proc:0x007fdb1289f888@(irb):4>"
=> "#<Proc:0x007fdb1289f888@(irb):4>"

Implicit Blocks

  • are not objects and cannot be assigned to variables
  • method does not have a variable to reference the block, it is not a parameter, only yield and Kernel#block_given? can 'see' the block
  • yield keyword itself is not really a method/variable
  • use the block_given? method to determine if a block was passed, otherwise, a LocalJumpError will be raised when you try to yield without a block
  • but: much faster! huge performance benefit (for certain Ruby implementations, ie MRI)
  • note that a return within a block will return from the enclosing method
  • cons: can only pass in one block to a method
# using an implicit block + yield
def calculate(a, b)
  yield(a, b) if block_given?
end

>> calculate(2, 3)
=> nil
>> calculate(2, 3) { |a, b| a + b }
=> 5

# return from within a block returns from the surrounding method
def calculate(a, b)
  yield(a, b)
  return 10
end

>> calculate(2, 3) { |a, b| return 7 }
=> 7

# (note that this method needs to be called from inside a method
# if you are in irb, as you can't return from the `main` context)

To stick to the principle of least surprise, just don't ever use return inside a block.

Aside: Object mixes in Kernel, that's why Kernel methods are like global functions, so Kernel#block_given? is accessible.

Explicit Blocks / Procs

  • is an object of class Proc
  • made by having the method's last parameter start with & like&blah
  • you can explicitly pass in a Proc: method(&ref_to_a_block)
  • you can also implicitly pass in a block {}, Ruby converts it to a Proc for you
  • the &blah parameter is a Proc object, or nil if no block is passed in
# &block is an explicit (named) parameter
def calculation(a, b, &block)
 block.call(a, b)
end

puts calculation(5, 5) { |a, b| a + b }

Procs

  • unlike lambdas, the arguments are flexible. Excess arguments are silently discarded, missing/shortfall of arguments is made up with nil.

Lambdas

  • are objects, just like everything else in Ruby (of class Proc)
  • can be assigned to variables
  • resolves to an expression just like a method (the return value / last expression)
  • can take parameters just like a method
  • unlike Procs, the arguments must exactly match the definition
# resolves to an expression
lamb = lambda { 'Do or do not' }

>> lamb.call
=> "Do or do not"

# accepts parameters
lamb2 = lambda { |x| x + 1 }

>> lamb2.call 2
=> 3

Usage

Lambdas can be received as an explicit parameter.

# can use a lambda as an explicit parameter
def calculate(a, b, operation)
 operation.call(a, b)
end

>> calculate(2, 3, lambda { |a, b| a + b })
=> 5

Methods

Object#method(sym)

Comparison

Assuming 1.9+, and by 'blocks' we mean implicit blocks without &blah parameter in method:

... block Proc lambda
creating one tack on { ... } after method invocation Proc.new { ... } lambda { ... }
is an object? no yes, Proc yes, special Proc
can be assigned to variable? no yes yes
how to pass to method? tack on after method invocation for the &blah parameter, either tack on a block after method invocation to get it implicitly converted to a Proc or pass in a variable pointing to a Proc prefixed with & as the last parameter.
otherwise pass in as a normal variable pointing to the Proc
pass in as a normal variable pointing to the lambda
how to invoke inside method? yield only for the &blah parameter you can either use yield or blah.call.
for normal parameters you must use widget.call
widget.call
number of arguments flexible, excess is discarded, shortfall made up with nil flexible, excess is discarded, shortfall made up with nil must match
what if you return from inside? terminates enclosing method and returns the value specified inside block (acts like code snippet) terminates enclosing method and returns the value specified inside Proc (acts like code snippet) return value specified inside lambda to enclosing method, which continues running. (acts like a method)
how many can be passed into a method? at most one, using {} using &blah parameter (whether using {} or a variable at invocation), at most one. using parameters, many using parameters, many

Where:
pp = Proc.new { |a, b| a * b }
ll = lambda { |a, b| a * b }

Thing \ Receiver calculate(a, b)
yield(a, b)
calculate(a, b, &block)
yield(a, b)
OR block.call(a, b)
calculate(a, b, block)
block.call(a, b)
Block `calculate(2, 3) { a, b a * b }`
Proc calculate(2, 3, &pp) calculate(2, 3, &pp) calculate(2, 3, pp)
Lambda
calculate(2, 3, &ll) calculate(2, 3, &ll) calculate(2, 3, ll)

Mixing things up

  • when a method expects an explicit block parameter, you can pass it a block {}
  • when a method expects an implicit block, you can pass it a block variable &variable_name
# yield calls an implicit (unnamed) block 
def calculation(a, b)
  yield(a, b)
end

addition = lambda {|x, y| x + y}
puts calculation(5, 5, &addition)

Sources

@spanuska
Copy link

This is a super helpful guide - thanks for sharing this! I have a clarifying question. What do you mean by the 5th bullet under Implicit Blocks?

but: much faster! huge performance benefit (for certain Ruby implementations, ie MRI)

@wolfjohns
Copy link

wolfjohns commented Mar 7, 2017

Good guide but what is with the widget.call? In your comparison table for Proc and lambda "how to invoke inside method?" you would use the call method on the parameter being referenced. Example you have defined a method def hello (p); p.class; p.call end
b = proc or Proc.new {puts "Hi World!"}; hello(b)

[2] pry(main)> def hello(p)
[2] pry(main)* p p.class
[2] pry(main)* p.call
[2] pry(main)* end
=> :hello
[3] pry(main)> b = proc {puts "Hi World!"}
=> #Proc:0x00000001632698@(pry):6
[4] pry(main)> hello(b)
Proc
Hi World!
=> nil
[5] pry(main)> d = Proc.new {puts "Days and Nights"}
=> #Proc:0x000000015ba300@(pry):8
[6] pry(main)> hello(d)
Proc
Days and Nights
=> nil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment