|
#---Blocks---
|
In most languages, you can pass a function as argument
to another function.
In Ruby, you can do that via a
block which is just an anonymous function.
|
|
Before seeing how to define a function that
takes a block as an argument, and how to call
such a block within the function,
let’s do the same in Javascript first!
It will be easier to understand.
|
|
In Javascript, here’s how you define a
function that takes another function as
one of its arguments:
(You can run this by copy pasting the code into the
javascript console of developer tools in your browser)
|
## Beginning of Javascript code
function GreetAll(names, fnGreeter) {
return names.map(fnGreeter)
}
|
With a list of names like this:
|
let names = ["Brutha", "Vorbis", "Didactylos", "Nhumrod"]
|
Here’s how GreetAll will be called:
|
let result = GreetAll(names, function(name) {
let hi = (name == "Vorbis") ? "HI" : "hi"
return `${hi} ${name}`
})
console.log(result)
##["hi Brutha","HI Vorbis","hi Didactylos","hi Nhumrod"]
## End of Javascript code
|
Here’s what GreetAll does:
The passed-in function fnGreeter is called with each
name in names , and the results are all collected in
a new array and returned.
|
|
Now let’s do it in Ruby.
|
|
A block passed to a function can be called within
the function using the yield keyword.
|
def greet_all(names)
names.map { |name| yield(name) }
end
|
And here’s how you’ll call greet_all and pass-in
a block.
|
|
|
names = ["Brutha", "Vorbis", "Didactylos", "Nhumrod"]
result = greet_all(names) do |name|
hi = (name == 'Vorbis') ? 'HI' : 'hi'
"#{hi} #{name}"
end
p result
##["hi Brutha","HI Vorbis","hi Didactylos","hi Nhumrod"]
|
As mentioned in the beginning, a block is just an
anonymous function. All the general properties of a
function applies to it. Such as:
- The last line is the return value of the block
- You can define it to take multipe arguments
- If you want to pass a block that takes
keyword arguments, optional arguments etc,
you can do so.
|
|
Here’s a detailed example:
|
def transform(names)
res = names.map { |name| name.upcase }
if block_given?
res = res.map { |name| yield(name, prefix: 'HI') }
end
res
end
|
|
names = ["Brutha", "Vorbis", "Didactylos", "Nhumrod"]
|
Calling transform without a block:
|
res = transform(names)
p res
## ["BRUTHA", "VORBIS", "DIDACTYLOS", "NHUMROD"]
|
Calling transform with a block:
|
res = transform(names) do |name, prefix:, exclaim: true|
fail "Prefix is mandatory" unless prefix
"#{prefix} #{name}#{'!' if exclaim}"
end
p res
## ["HI BRUTHA!", "HI VORBIS!", "HI DIDACTYLOS!",
## "HI NHUMROD!"]
|
Calling transform with a block, but this time with a
different transformation logic:
|
res = transform(names) do |name, prefix:|
"#{prefix} #{name}"
end
p res
## ["HI BRUTHA", "HI VORBIS", "HI DIDACTYLOS",
## "HI NHUMROD"]
|
When to define a method with block support?
As you can see in these 3 examples, the purpose of
passing a block (or more generally a function) is that
the caller gets to define some aspect of the outcome.
|
|
More about blocks
- If you define a function that uses
yield , but
call it without passing a block, Ruby won’t raise
an error. Passing a block is optional. But you can
check if a block is passed with the block_given?
method
- You can only pass at most one block to a function.
If you want to pass-in more functions as arguments,
you must use lambdas or procs
- It’s one of the few things in Ruby that’s not
an object. You can only call it. You can’t call anything
on it.
|
|