2

I am comfortable with the following:

def some_def(foo, &block)
    puts "block utilized below"
    block.call(foo)
end

def some_other_def(bar)
    puts "using yield below"
    yield bar
    puts "and back into the method"
end

So I have learned to keep blocks (and procs) separate from the yield keyword.

However, I ran into the following code:

# ./open_file.rb

class File
    def self.open(name, mode, &block)
        file = new(name, mode)
        return file unless block_given?
        yield(file)
    ensure
        file.close
    end
end

It seems the parameter &block does not matter when I implement execute this code in irb:

irb -r ./file.open.rb

and do something like:

File.open('foo.txt','r') {|f| puts f}

Is &block rendered optional by block_given? in:

return file unless block_given?
sawa
  • 165,429
  • 45
  • 277
  • 381
foo
  • 195
  • 1
  • 8
  • You can use either form, interchangeably. – user229044 Mar 04 '19 at 03:22
  • 1
    `&block` parameter is _always_ an optional parameter, with or without `block_given?`. However, running `block.call` (or `yield`, for that matter) when `block` is `nil` (which is the default value when it is not provided) will break things; so when `&block` is used, usually you'll _want_ to check `block_given?`. Thus, you have a case of _reverse causality_: It's not that `block_given?` causes `&block` to be optional; it's that `&block` being optional makes programmers use `block_given?`. – Amadan Mar 04 '19 at 04:00
  • @Amadan you can simply check via `if block` in that case. – Stefan Mar 04 '19 at 08:07
  • @Stefan: Yes, AFAIK the two are synonymous if there is an explicit block parameter. Maybe I was misreading OP's question? If OP meant "Is `&block` rendered _unnecessary_ by `block_given?`", then here, in a way, the answer is yes. – Amadan Mar 04 '19 at 08:12

2 Answers2

5

Generally, you only use the &block argument if you need to pass the block to another method such as in this made up example:

def m(&block)
  some_array.map(&block)
end

or this real version of Enumerable#sum from Rails:

def sum(identity = nil, &block)
  if block_given?
    map(&block).sum(identity)
  else
    sum = identity ? inject(identity, :+) : inject(:+)
    sum || identity || 0
  end
end

In either case, the block that the method is called with is used with another method call so you need a way to refer to the block (i.e. a name).

So block_given?/yield and &block serve different purposes. Being able to call block_given? doesn't make &block redundant and, as in the #sum implementation above, they can even be used together.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Wonder why they didn't use `inject(identity) { |sum, e| sum + yield(e) }` instead and a default `identity` value of `0` instead of `nil`. – Stefan Mar 04 '19 at 08:48
1

The &block in the method signature accepts a block, converts it into a proc, and assigns it to a variable named block. In case a block is not provided, block is assigned nil.

Whether to use the argument block within the method definition does not matter just like it does not matter whether an ordinary method argument bar is used or not in the following definition:

def foo(bar); end

However, accepting a block as a parameter and not using it is redundant and waste of resource. It may perhaps still make sense to explicitly indicate to a fellow programmer that the method accepts a block.

Using block_given? is independent of all this. It is independent of whether a block has been accepted as an argument via &. It refers to the block directly, irrespective of block.

sawa
  • 165,429
  • 45
  • 277
  • 381