Hommer Smith
Hommer Smith

Reputation: 27852

When to use method { } vs method(&block)?

When we want to pass blocks to a method, when do we want to do:

block = Proc.new { puts 'test blocks & procs' }
def method(&block)
  yield
end

VS

def method
  yield
end

method { puts 'test blocks & procs' }

Is there any particular circumstance we would want to use one or the other?

Upvotes: 0

Views: 460

Answers (3)

Dimitris Zorbas
Dimitris Zorbas

Reputation: 5451

Block seems to be a bit faster based on the following benchmark:

require 'benchmark/ips'

prc = Proc.new { '' }

def _proc(&block)
  yield
end

def _block
  yield
end

Benchmark.ips do |x|
  x.report('Block') { _block { '' } }
  x.report('Proc') { _proc(&prc) }
end

Benchmark results on an i7-4510U CPU @ 2.00GHz

           Block   149.700k i/100ms
            Proc   144.151k i/100ms
           Block      4.786M (± 1.6%) i/s -     23.952M
            Proc      4.269M (± 2.3%) i/s -     21.334M

Upvotes: 0

7stud
7stud

Reputation: 48599

1) A block is not an object, and therefore a block cannot be captured in a variable, and a block cannot be passed explicitly to a method. However, you can convert a block to a Proc instance using the & operator, and a Proc instance is an object which can be assigned to a variable and passed to a method.

2) Proc.new() doesn't return a block--it returns a Proc instance. So naming your variable block is misleading.

3) yield only calls a block, which is the thing specified after a method call:

do_stuff(10) {puts 'hello'} #<-- block

do_stuf(10) do |x| #<--'do' marks the start of a block
  puts x + 2
end             #<--end of block

block = Proc.new {puts 'hello'} 
 ^
 |
 +--- #not a block

yield does not call a Proc instance that happens to be passed as an argument to a method:

def do_stuff(arg)
  yield
end

p = Proc.new { puts 'test blocks & procs' } 
do_stuff(p)

--output:--
1.rb:2:in `do_stuff': no block given (yield) (LocalJumpError)
from 1.rb:6:in `<main>'

Compare to:

def do_stuff(arg)
  puts arg
  yield
end

do_stuff(10) {puts "I'm a block"}

--output:--
10
I'm a block

4) You can convert a block to a Proc instance and capture it in a variable using &, like this:

def do_stuff(arg, &p)
  puts arg
  p.call
end

do_stuff(10) {puts "I'm a block"}

--output:--
10
I'm a block

Liar! You are not a block anymore! Typically, the variable name used to capture a block is written as &block:

def do_stuff(arg, &block)
  puts arg
  block.call   
end

But that's technically incorrect; the block variable will contain a Proc instance, as you can see here:

def do_stuff(arg, &block)
  puts arg
  puts block.class
end

do_stuff(10) {puts "I'm a block"}

--output:--
10 
Proc

5) You can also use the & operator to convert a Proc instance to a block, as Nobita's answer demonstrated:

def do_stuff(x, y)
  yield(x, y)
end

p = Proc.new {|x, y| puts x+y} 
do_stuff(10, 20, &p)

--output:--
30

In that example, the method call do_stuff(10, 20, &p) is equivalent to writing:

do_stuff(10, 20) {|x, y| puts x+y}

6) When do you want to use a block and yield v. &block and call?

One use case for capturing a block in a variable is so that you can pass it to another method:

def do_stuff(arg, &a_proc)
  result = arg * 2
  do_other_stuff(result, a_proc)
end

def do_other_stuff(x, p)
  1.upto(x) do |i|
    p[i]   #Proc#[] is a synonym for Proc#call
  end
end

do_stuff(2) {|x| puts x}

--output:--
1
2
3
4

I suggest you operate by these two rules:

  1. When you write a method that expects a block, ALWAY execute the block with yield.
  2. If #1 doesn't work for you, consider capturing the block and using call (or [])

Upvotes: 1

Nobita
Nobita

Reputation: 23713

Using Procs lets you not repeat yourself. Compare this:

arr1 = [1,2,3]
arr2 = [4,5,6]

Using blocks, you repeat the block twice:

arr1.map { |n| n * 2 }
arr2.map { |n| n * 2 }

When using Procs, you can reuse the object:

multiply_2 = Proc.new do |n|
  n * 2
end

arr1.map(&multiply_2)
arr2.map(&multiply_2)

Upvotes: 3

Related Questions