~/src/www.mokhan.ca/xlgmokha [main]
cat blocks-procs-and-lambdas.md
blocks-procs-and-lambdas.md 11128 bytes | 2014-04-07 20:25
symlink: /opt/ruby/blocks-procs-and-lambdas.md

blocks procs and lambdas

Blocks

Blocks are closures that can be passed to any method in ruby. A block gets special handling and does not need to be explicitly defined in a methods parameter list.

def dont_tell()
  yield "i sleep with a blanky." if block_given?
end

dont_tell do |dirty_secret|
  puts dirty_secret
end

dont_tell

In the above example, the method dont_tell is defined and yields execution back to an implicit block argument via the yield keyword. Line 5 invokes the dont_tell method passing along a block parameter that accepts a secret as a single parameter and writes it to stdout.

Line 9 also invokes the dont_tell method but without an explicit block parameter. The call to Kernel#block_given? prevents an attempt to invoke a block if one is not given.

Block’s can be converted to Proc’s using an & and back to a block using &.

def will_it_blend?(&block)
  [1,3,5,7,9,10].each(&block)
end

will_it_blend? do |number|
  if number.even?
    raise "Boom"
  else
    print "."
    sleep 1
  end
end

On line 1 the block is converted to a block, then on line 2 the proc is converted back to a block and passed to each.

Proc

A proc is an object that represents a block of code. A Proc can be created in a couple of ways.

Proc.new { puts "holla!" }
proc do |x|
  puts "#{x} here comes the boom!"
end

You can invoke a proc by calling it’s call method.

bomb = proc do |code|
  raise "heck" unless code == 42
end

bomb.call(1)

Lambda

A lambda is a special kind of proc. To create one you can invoke Kernel#lambda.

bomb = lambda do |code|
  raise "heck" unless code == 42
end

bomb.call(42)

There are two main differences between a proc and a lambda.

  1. arity
  2. return

arity

when invoking a lambda the number of parameters passed in must match the number of parameters defined in the definition.

irb(main):009:0> greeter = lambda { |name| puts "hi #{name}" }
=> #<Proc:0x007fe8eb095de0@(irb):9 (lambda)>
irb(main):010:0> greeter.call
ArgumentError: wrong number of arguments (0 for 1)
  from (irb):9:in `block in irb_binding'
  from (irb):10:in `call'
  from (irb):10
  from /Users/mo/.rbenv/versions/1.9.3-p545/bin/irb:12:in `<main>'
irb(main):011:0>

procs don’t care.

irb(main):011:0> greeter = proc { |first_name, last_name| puts "hello #{first_name} #{last_name}" }
=> #<Proc:0x007fe8eb07abd0@(irb):11>
irb(main):012:0> greeter.call
hello
=> nil

return

When returning from a lambda, it exits the scope of the lambda back to the caller. Very much like a method invocation.

class Command
  def run(number)
    callable = lambda do |x|
      puts "#{x} entered callable"
      return if x.even?
      puts "#{x} leaving callable"
    end
    greet(callable, number)
  end

  def greet(callable, n)
    puts "#{n} entered greet"
    callable.call(n)
    puts "#{n} leaving greet"
  end
end

command = Command.new
command.run(2)

When this example is run returning from the lambda only returns from the lambda. Execution returns to the calling method.

$ ruby blocks.rb
2 entered greet
2 entered callable
2 leaving greet

If we run the same example again, only swap a lambda for a proc. Then we see the following output.

$ ruby blocks.rb
2 entered greet
2 entered callable

When we return from a proc, it exits the method that called the proc.