admin管理员组

文章数量:1122832

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 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])

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])
Share Improve this question edited Dec 16, 2024 at 16:53 user513951 13.6k7 gold badges70 silver badges89 bronze badges asked Nov 22, 2024 at 18:46 Colin WuColin Wu 6991 gold badge8 silver badges25 bronze badges 3
  • some_method accepts a block, technically all ruby methods do, regardless of whether or not it is acknowledged. What some_method does from there no one can say. If some_method also accepts an argument then it is possible that the 2 are equivalent if some_method uses the result of the block evaluation the same way it uses the argument but again no one can say based on the question. Please provide a specific example of the some_method in question and then maybe we can answer the questions posed. – engineersmnky Commented Nov 22, 2024 at 19:05
  • Similar question here but on second look, I think it's not quite similar enough to mark as "duplicate." – user513951 Commented Nov 22, 2024 at 20:38
  • These might help: why pass block arguments to a function in ruby? (more specific) Blocks and yields in Ruby (more general). – Stefan Commented Nov 23, 2024 at 10:25
Add a comment  | 

2 Answers 2

Reset to default 4

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.

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.

本文标签: methodsWhat39s the difference between passing an argument and passing a block in RubyStack Overflow