Ruby block
I’ve been trying to figure out how it works. This morning I wanted to write it down my thinking process today, before I forget.
block_given?
block_given? is a method of Kernel module. In other words, it’s one of the same kind of puts
, gets
, sleep
, etc. They are at the top-level, so to speak.
The Kernel module is included by class Object, so its methods are available in every Ruby object.
And block_given?
verifies if block is given when you call a method.
What do you mean by “block is given”? In normal cases (when block is not given) the codes should be like this:
object_name.method_name(parameters)
object_name.method_name
Here you call methods with parameters, or without parameters. But when you say “block is given” (However this is also normal, nothing special) that means you called it with a block. A lot like a parameter.
object_name.method_name(parameter_names) { chunks of code }
object_name.method_name { chunks of code }
Let’s try to do some meaningful examples, define methods without blocks, and then with blocks.
def hello(param_name)
puts "Hello, #{param_name}"
end
hello("Jimmy")
# "Hello, Jimmy"
Everything looks still fine, but only difference will be, you put a &
sign there and call the Proc#call method from the block.
def hello(param_name, &block_name)
block_name.call(param_name)
end
hello("Jimmy") { |name| puts "Hello, #{name} on the block" }
# "Hello, Jimmy on the block"
Let’s see if we can use the keyword block_given?
and make a block optional. Now hello
method should be useful with a block or without a block. By the way, it’s a convention to use “block” in place of block_name
, as a block name.
def hello(param_name, &block)
if block_given?
block.call(param_name)
else
puts "Hello, #{param_name}"
end
end
hello("Jimmy")
# "Hello, Jimmy"
hello("Jimmy") { |name| puts "Hello, #{name} on the block" }
# "Hello, Jimmy on the block"
Usually we do it with some value in the object (some state of the object) as follows.
class Friend
def initialize(name)
@name = name
end
def hello(&block)
if block_given?
block.call(@name)
else
"Hello, #{@name}"
end
end
end
friend = Friend.new("Jimmy")
puts friend.hello
# "Hello, Jimmy"
puts friend.hello { |name| "Hello, #{name} on the block" }
# "Hello, Jimmy on the block"
For the last thing, but not least, there’s yield
. (Probably I guess this is the most important thing in this post)
Yield #
You don’t need to name a block. There’s yield
instead of &block
or block.call
. In addition, yield
runs faster than block.call
, they say.
Many people prefer block.call
because it’s clear.
If one method makes your code clearer at the expense of a millionth of a second, use it anyway.
Check out yourself if it’s still equivalent when they changed the code with the yield
keyword. Since we didn’t make it optional or use block_given?
here, it’s also a good chance to experience their errors.
def bye(&block)
block.call
end
bye { "Bye-bye" }
# "Bye-bye"
bye
# NoMethodError
def bye
yield
end
bye { "Bye-bye-bye" }
# "Bye-bye-bye"
bye
# LocalJumpError
I’m not sure which error is better. They are anyways expecting a block, so you might need block_given?
.
–
Interesting Links: #
- http://www.skorks.com/2013/04/ruby-ampersand-parameter-demystified/
- http://rubylearning.com/satishtalim/ruby_blocks.html
- https://innig.net/software/ruby/closures-in-ruby
- https://pine.fm/LearnToProgram/chap_11.html
- https://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/18-blocks
–