This content originally appeared on DEV Community 👩💻👨💻 and was authored by Franciscello
The other day while working on the Blocks section for the Crystal's tutorial, I came across something interesting about blocks, methods and overloading.
Overloading
Let's start by reviewing the concept of method overloading in Crystal.
In Crystal we can define methods with the same name but some difference and for the compiler they will be seen as different methods.
Here is an example:
def transform(str : String) : Int32
str.size
end
def transform(n : Int32) : Int32
n + 1
end
transform("Crystal") # => 7
transform(41) # => 42
In the above example we have two methods with the same name transform
but the difference is the type restriction of the parameter.
Here is another example:
def transform(str : String)
yield str
end
def transform(str : String)
str.capitalize
end
transform("Crystal") { |str| "Hello #{str}" } # => "Hello Crystal"
transform("crystal") # => "Crystal"
In this last example, the difference between both methods is that the first one receives a block, in addition to the String
parameter. To make this difference more clear, we can add the block parameter explicitly:
def transform(str : String, & : String -> String)
yield str
end
def transform(str : String)
str.capitalize
end
transform("Crystal") { |str| "Hello #{str}" } # => "Hello Crystal"
transform("crystal") # => "Crystal"
The following is the list of differences that allows overloading a method:
- The number of parameters
- The type restrictions applied to parameters (first example)
- The names of required named parameters
- Whether the method accepts a block or not (last example)
🤔 What if ...
As I was writing about blocks, I wonder if the type restrictions over a block parameter could allow method overloading.
For example, are the following transform_string
methods considered different?
def transform_string(word : String, & : String -> String)
block_result = yield word
puts block_result
end
def transform_string(word : String, & : Int32 -> String)
block_result = yield word.size
puts block_result
end
transform_string "crystal" do |word|
word.capitalize
end
transform_string "crystal" do |number|
"#{number}"
end
Note: The first method defines a block parameter of type String -> String
. And the second method defines a block of type Int32 -> String
.
The output was:
Error: undefined method 'capitalize' for Int32
Oops! Not the expected output 😅
The problem is that, for the compiler, the second method is the only definition for transform_string
(meaning, the first definition is simply overridden by the second one). And this happens because the compiler does not use blocks for method overloading. Here is the why (in Johannes' words):
Block arguments are defined by the method that yields, not by how the method is called; the yielding method can’t behave differently depending on the block. The compiler needs to find the method before it looks at the block, which means block arguments cannot be used to find the method.
But, what if ...
Blocks and Procs
After talking to Beta and Johannes, they proposed a solution very close to what we are trying to implement:
We can use a Proc (created from a captured block) as the methods' argument.
🤯
First, let's see an example on how a Proc
is created from a captured block:
proc = Proc(Int32, String).new { |x| x.to_s }
typeof(proc) # Proc(Int32, String)
# when can invoke it using `call`:
proc.call 42 # => "42"
So now we need to change the transform_string
definitions and implementations, like this:
def transform_string(word : String, block : String -> String)
block_result = block.call word
puts block_result
end
def transform_string(word : String, block : Int32 -> Array(String))
block_result = block.call word.size
puts block_result
end
We have replaced the block parameter with a Proc
parameter, and related to this change, we are using the method call
to invoke the Proc
😎.
Let's see if this works as expected:
def transform_string(word : String, block : String -> String)
typeof(block) # => Proc(String, String)
block_result = block.call word
puts block_result
end
def transform_string(word : String, block : Int32 -> Array(String))
typeof(block) # => Proc(Int32, Array(String))
block_result = block.call word.size
puts block_result
end
proc_string_string = Proc(String, String).new do |word|
word.capitalize
end
transform_string("crystal", proc_string_string)
proc_int32_array = Proc(Int32, Array(String)).new do |number|
["#{number}"]
end
transform_string("crystal", proc_int32_array)
The output:
Crystal
["7"]
It worked! Yeah! 🤓🎉
Let's rewrite the code in a more concise way:
def transform_string(word : String, block : String -> String)
typeof(block) # => Proc(String, String)
puts block.call word
end
def transform_string(word : String, block : Int32 -> Array(String))
typeof(block) # => Proc(Int32, Array(String))
puts block.call word.size
end
transform_string "crystal", Proc(String, String).new { |word| word.capitalize }
transform_string "crystal", Proc(Int32, Array(String)).new { |number| ["#{number}"] }
And it's working perfectly! 🤩
🤔 What if ... (part 2 🛹)
We got our code working but what if we could use a block to overload methods (ie. the original idea).
Maybe the compiler would need more information on the parameter's type restriction when defining the block 🤔. Something like this:
transform_string "crystal" do |word : String|
word.capitalize
end
This way the compiler can "see" that we are defining a block, which input is a String
and also returns a String
and so can select the correct implementation for transform_string
:
def transform_string(word : String, &block : String -> String)
puts yield word
end
transform_string "crystal" do |word : String|
word.capitalize
end
Farewell and see you later
Let's recap:
We have reviewed some interesting concepts (method overloading, Blocks and Procs) and we have a way to overload methods using the type restrictions of the Blocks Proc parameter.
Hope you enjoyed it! 😃
This content originally appeared on DEV Community 👩💻👨💻 and was authored by Franciscello
Franciscello | Sciencx (2023-01-11T23:07:45+00:00) Blocks and methods overloading. Retrieved from https://www.scien.cx/2023/01/11/blocks-and-methods-overloading/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.