Blocks and methods overloading
Tags: crystal, todayilearned, beginners
Published: 2023-01-11 23:07:45

The other day while working on the _Blocks_ section for the [Crystal's tutorial](https://github.com/crystal-lang/crystal-book/pull/655), I came across something interesting about blocks, methods and overloading.

## Overloading

Let's start by reviewing the concept of [method overloading in Crystal](https://crystal-lang.org/reference/latest/syntax_and_semantics/overloading.html).

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:

```crystal
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:

```crystal
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:

```crystal
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?

```crystal
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:

```terminal
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, explained in a [comment](https://forum.crystal-lang.org/t/method-call-depending-on-block-type/5252/3) by [Johannes](https://forum.crystal-lang.org/u/straight-shoota):
> 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](https://forum.crystal-lang.org/u/beta-ziliani) and [Johannes](https://forum.crystal-lang.org/u/straight-shoota), they proposed a solution very close to what we are trying to implement: 

> We can use a [Proc](https://crystal-lang.org/reference/latest/syntax_and_semantics/literals/proc.html) (created from a [captured block](https://crystal-lang.org/reference/latest/syntax_and_semantics/capturing_blocks.html)) as the methods' argument.

🤯

First, let's see [an example](https://crystal-lang.org/reference/latest/syntax_and_semantics/literals/proc.html) on how a `Proc` is created from a _captured block_:

```crystal
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:

```crystal
def transform_string(word : String, block : String -> String)
  block_result = block.call word
  puts block_result
end
 
def transform_string(word : String, block : Int32 -> 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:

```crystal
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 -> String)
  typeof(block) # => Proc(Int32, 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, String).new do |number|
  "#{number}"
end
transform_string("crystal", proc_int32_array)
```

The output:

```terminal
Crystal
"7"
```

It worked! Yeah! 🤓🎉

Let's rewrite the code in a more concise way:

```crystal
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 -> String)
  typeof(block) # => Proc(Int32, String)
  puts block.call word.size
end

transform_string "crystal", Proc(String, String).new { |word| word.capitalize }
transform_string "crystal", Proc(Int32, String).new { |number| "#{number}" }
```

And [it's working](https://play.crystal-lang.org/#/r/edrh) perfectly! 🤩

## 🤔 What if ... (part 2 🛹)

We got our code working but at the same time we discovered that the compiler can not distinguish two methods differing only in the _block_’s type. 

So let’s think on **how we could improve Crystal to allow it**.

Here is a proposal: maybe we can give more information to the compiler about the parameter's type restriction when defining the _block_ 🤔. Something like this:

```crystal
transform_string "crystal" do |word : String| 
  word.capitalize
end
```

This way the compiler can "see" that we are defining a _block_, whose input is a `String` and also returns a `String` and so can select the correct implementation for `transform_string`:

```crystal
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! 😃

Thanks, thanks, thanks to [Beta](https://forum.crystal-lang.org/u/beta-ziliani) and [Johannes](https://forum.crystal-lang.org/u/straight-shoota) for reviewing this post and improving the code and text!! 👏👏👏