I am new to Ruby. I have used a lot of C# and JavaScript which allow higher-order functions and I typically use them on a daily basis.
Ruby seems a little strange to me though. An each
function might look like:
def each
@items.each do |item|
yield(item)
end
end
items.each { |item| puts item }
Yet Ruby also has some support for higher-order functions. The above could be rewritten to something like:
def each(proc)
@items.each do |item|
proc.call item
end
end
items.each -> (item) { puts item } # Or...
items.each lambda { |item| puts item }
Or even:
def each(&proc)
@items.each do |item|
proc.call item
end
end
# No difference in syntax.
items.each { |item| puts item }
Which is more on par with most other languages, and is just a few characters longer. Instead of explicitly passing in a block, everything seems to use yield
.
yield
itself seems crazy, magical, and mysterious. After all, it's going to the origin of the call and grabbing a block immediately following the call. This seems bizarre and unnatural, and I'm not aware of any parallel of this feature in another language.
So what's the deal with yield
?
This example of yield:
def do_something_for_each(array)
array.each do |el|
yield(el)
end
end
Is just syntax sugar for:
def do_something_for_each(array, &block)
array.each do |el|
block.call(el)
end
end
Pick the syntax you like and run wild with it.
[Yield is] going to the origin of the call and grabbing a block immediately following the call.
Not really. yield
passes an argument to a block; it doesn't "grab a block" or do anything with it. In other words, this:
def foo; yield self; end
foo { |x| x.inspect }
# => "main"
Here, yield
isn't doing anything but passing an argument to the block that is passed into the foo
method. Every Ruby method supports an optional block—except when a block is actually mandatory—so the only "magic" is that the language allows a block to be passed even when it isn't explicitly declared as part of the method signature.
To see this implicit signature in action, consider this:
def foo; puts block_given?; end
foo { |x| x.inspect }
which will print "true" and return nil
, which is the expected return value from the puts
method.
Of course, without yield
the block doesn't do anything at all. For example:
def foo; end
foo { |x| x.inspect }
# => nil
One advantage of yield
is it also lets you use next
(like continue
) and break
. In other languages, for next
, you might have to use return
, and for break
, you might have to (ab)use exceptions. It is arguably nicer to have built-in support for these sorts of operations.
In most cases, you execute the block right there in the method, using yield.
The block is passed straight into the method, and the method can then call back to the block with the yield keyword.
def a_method(a, b)
a + yield(a, b)
end
a_method(1, 2) {|x, y| (x + y) * 3 } # => 10
When you call back to the block, you can provide values for its arguments, just like you do when you call a method. Also, like a method, a block returns the result of the last line of code it evaluates.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With