Reputation: 699
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
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 usingval1
,val2
, andval3
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
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