Colin Wu
Colin Wu

Reputation: 699

What's the difference between passing an argument and passing a block in Ruby?

I am trying to understand how this piece of ruby works. (Please forgive me if it's been asked before but I could not find anything here or on ruby-lang.org documents.)

The code is:

some_method do
  # lines that look like parameters; e.g.
  [val1, val2, val3]
end

where some_method seems to create a hash using val1, val2, and val3 as keys.

Would this be the same as

some_method([val1, val2, val3])

Upvotes: 2

Views: 100

Answers (2)

user513951
user513951

Reputation: 13715

Would this be the same

Not automatically, no.

Let's examine how both versions would normally work, and then we can construct a version that allows them to operate the same.


To achieve the result

some_method … creates a hash using val1, val2, and val3 as keys

from the code you supplied, the method definition would look like something this:

def some_method
  # Get the return value from the block, e.g. [val1, val2, val3]
  array = yield

  # Generate a hash using the array elements as keys.
  # The right-hand values here are just numbers counting up from zero.
  array.zip(array.length.times.map).to_h
end

Here's what happens when you call this method as in your first code block:

val1, val2, val3 = :val1, :val2, :val3

result = some_method do
  [val1, val2, val3]
end

puts result
# => { val1: 0, val2: 1, val3: 2 }

Your second code block results in the error ArgumentError (wrong number of arguments (given 1, expected 0)), because some_method does not take any positional arguments.


If you wanted to write some_method to produce the same result for the second code block, it would look like this:

def some_method(array)
  array.zip(array.length.times.map).to_h
end

The difference is where the value array comes from. In the first def, it comes from the result of the block, obtained by calling yield. In the second def, it comes from the argument passed in as array.


If you wanted a single method that would work either way, it might look like this:

def some_method(array=nil)
  if array.nil? && block_given?
    array = yield
  end

  if array
    array.zip(array.length.times.map).to_h
  end
end

This works because it first checks whether an array was passed as a regular argument, and uses it if so (ignoring any block given without even executing it, incidentally).

If no array was passed and a block was given, it uses the result of the block instead. If neither was given, it returns nil.

There are other, more complete ways to implement this (see comments, or answers to this question), but hopefully this sample implementation makes it clear that extra work is needed to achieve the behavior you asked about.


I can't find any explicit coverage of how yield works at ruby-lang.org, but blocks are covered here and block_given? is here. Here is a very old explanation of yield, but it's still accurate.

Upvotes: 1

Hector Correa
Hector Correa

Reputation: 26690

When you call a method in Ruby you can pass a block and if the method expects the block it can execute it.

In the code below some_method checks if a block was received (block_given?) and then executes it (yield):

def some_method
  puts "hello"
  if block_given?
    x = yield
    puts x
  end
  puts "bye"
end

puts "example one - no block given"
some_method()

puts "example two - block given"
some_method() do
  1 + 2
end

Passing a block (via do) is not the same as passing the parameters as values. Passing a block allows the method do execute any arbitrary piece of code defined in the block. In the example above I just do a simple math operation (1+2) but the block could have called another function, or read from a database, or anything.

Upvotes: 4

Related Questions