# Phân phối phương thức (Dispatch) trong Julia


## Hàm và Phương thức trong Julia

Trong Julia, ta có khái niệm **hàm** (functions) và **phuơng thức** (methods). Hàm đơn giản chỉ là 1 cái tên như: `push!` hay `read`. Phương thức là định nghĩa cụ thể của một hàm cho các kiểu đối số (types of arguments) nhất định, ví dụ như `push!(s::Set, x)` hoặc `read(io::IO)`. Đứng từ góc nhìn hướng đối tượng (object-oriented) bạn có thể xem các phương thức như là các thể hiện (instances) của hàm.

## Phân phối phương thức (dispatch)
Như vậy, mỗi khi trình biên dịch (compiler) Julia bắt gặp một hàm, nó sẽ xem xét kiểu (type) của các đối số và **chọn phương thức phù hợp nhất để thực thi**. Quá trình đó được gọi là **phân phối phương thức**, hay gọi tắt là **phân phối** (dispatch). Đây là một khái niệm quan trọng trong Julia, nó cho phép bạn viết mã (code) một cách tự nhiên và hiệu quả.

<!-- For any given method you can consider the dispatching as slicing a part of the entire possible type space of that given function. For a given set of arguments of course. If you increase the number of arguments in the function definition, then more dimensions get added to the type space. I don't even know how to find the best written words for this, the visualization above just feels intuitive to me. -->

Đối với bất kì phương thức cho trước nào, bạn có thể xem việc phân phối (dispatch) như là việc cắt ra (slicing) một phần từ không gian các kiểu khả dĩ (possible type space) của hàm đó ứng với một tập hợp các đối số nhất định (trong định nghĩa của hàm đó). Nếu bạn tăng số lượng đối số trong định nghĩa hàm, thì tức là bạn đang thêm nhiều chiều vào không gian kiểu. Xem hình minh hoạ dưới đây:

<div style="text-align: center; margin-top: 50px; margin-bottom: 50px;">
    <img    src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681976585586/63ee230c-90c2-41b1-b568-6eafa3b466e9.png" width=100% style="max-width: 100%; max-height: 200px; width: auto; height: auto;">
</div>

<!-- Let's take the function f and imagine for a moment that there are only 3 types in the whole Julia type universe: the Float64, Int64 and the String. The Float64 and the Int64 are a subtype of Number, which is obvious I hope. By default in Julia if you specify no type in your function argument, then it will be assumed you mean the Any type, of which every other type is a subtype. -->

Ví dụ, xét hàm `f`. Giả sử chỉ có 3 kiểu trong toàn bộ vũ trụ kiểu (type universe) của Julia: `Float64`, `Int64`, và `String`. `Float64` và `Int64` là các kiểu con (subtype) của `Number`. Mặc định trong Julia, nếu bạn không chỉ định (specify) kiểu cho đối số nào đó của hàm, thì đối số đó sẽ được giả định là có kiểu `Any`. Tất cả các kiểu khác đều là kiểu con của `Any`.

<!-- A method f(::Any, ::Any) thus describes the entire space of all possible types for the function named f . On the other hand, a method like f(::Int64, ::String) is super concrete, it's a singular point in the type space. -->

Một phương thức như `f(::Any, ::Any)` áp dụng cho mọi cặp đối số thuộc phạm vi của `Any`. Ngược lại, `f(::Int64, ::String)` chỉ áp dụng cho một tổ hợp kiểu cụ thể; trong minh hoạ dưới đây, tổ hợp đó tương ứng với một ô trong lưới kiểu.

<div style="text-align: center; margin-top: 50px; margin-bottom: 50px;">
    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681980418289/8da099db-a243-4908-8a55-b4e2a999fc0d.png" width=100% style="max-width: 100%; max-height: 400px; width: auto; height: auto;">
</div>


<!-- You can use abstract types like Number or unions like Union{Float64, Int64} to capture a subset of the discrete type space. This way you can choose which part you want to define for your function, with the chosen set of types you will be dispatching on at runtime. Abstract types in Julia exist only for this dispatching purpose, to dispatch on a set of subtypes, they have no other influence on their subtypes what so ever. They are not dictators like classes in other languages. -->

Bạn có thể sử dụng các kiểu trừu tượng (abstract types) như `Number` hoặc các hợp kiểu (unions) như `Union{Float64, Int64}` để bao quát một tập hợp con của không gian kiểu rời rạc. Bằng cách này, bạn có thể chọn một phần của không gian mà bạn muốn định nghĩa cho hàm. Trong quá trình chạy (runtime), Julia sẽ **phân phối** (dispatch) phương thức phù hợp cho hàm ứng với phần không gian kiểu con mà bạn đã chọn trước đó. Các kiểu trừu tượng trong Julia chỉ tồn tại cho mục đích chọn phương thức trên một tập hợp kiểu con, và chúng không có ảnh hưởng nào khác lên chính các kiểu con đó.

<!-- I like these visuals. Some junior engineers wonder what "diagonal dispatch" is. I don't have any other way of explaining the concept then by just drawing it. The figure is immediately obvious. Diagonal dispatch happens when the type of all arguments is forced to be equal with f(::T, ::T) where T . This truly represent a diagonal through the type space. You can see it in the example above. You can also limit the diagonal dispatch to a subset with f(::T, ::T) where T<:Number and in higher dimensions you can be fancy like f(::T, ::T, ::S) where {T<:Number, S<:AbstractString} by adding multiple of these parametric types. -->

## Phân phối chéo (diagonal dispatch)

Nếu bạn không biết **phân phối chéo** (diagonal dispatch) là gì, thì có thể xem minh hoạ dưới đây. Phân phối chéo xảy ra khi kiểu của tất cả các đối số bị ép buộc phải giống với `f(::T, ::T) where T`. Và định nghĩa này tương ứng với chéo xuyên qua không gian kiểu. Bạn cũng có thể giới hạn **phân phối chéo** vào một tập con với `f(::T, ::T) where T<:Number` và trong các không gian nhiều chiều hơn, bạn có thể định nghĩa phức tạp hơn như `f(::T, ::T, ::S) where {T<:Number, S<:AbstractString}` bằng cách thêm nhiều tham số kiểu (parametric types).

<div style="text-align: center; margin-top: 50px; margin-bottom: 50px;">
    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681977217318/3aaf2783-ca62-46f7-9854-99515f0c3818.png" width=100% style="max-width: 100%; max-height: 170px; width: auto; height: auto;">
</div>

<!-- Note that when you define a method twice, you have to take care that it is clear which method gets dispatched on. The compiler will prioritize the one that is most concrete, so the one that is most specific about the types. In the figure below, I ordered them from most specific to least specific. You can try for yourself to see if I ordered them correctly. -->

## Mức độ ưu tiên trong phân phối phương thức

Khi bạn định nghĩa một phương thức hai lần, bạn cần biết rõ lúc nào thì phương thức nào sẽ được chọn. Trình biên dịch sẽ ưu tiên phương thức nào cụ thể hơn, nghĩa là phương thức nào có khai báo kiểu rõ ràng hơn. Trong hình dưới đây, các phương thức được sắp xếp theo mức độ cụ thể giảm dần từ trái qua phải.

<div style="text-align: center; margin-top: 50px; margin-bottom: 50px;">
    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681980462724/38a9832e-1633-4457-864b-7c49b10d76bd.png" width=100% style="max-width: 100%; max-height: 300px; width: auto; height: auto;">
</div>

Ví dụ, nến bạn định nghĩa phương thức `f` như sau:

```julia
f(::Any, ::Any) = println("any & any")
f(::Int64, ::Int64) = println("int & int")
```

<!-- Then most function calls will run the broadest method because that one is defined for the whole type space, but when you input two integers you will call the very specific method f(::Int64, ::Int64) . Let's give it a go: -->

Phần lớn các lần gọi hàm sẽ được phân phối về phương thức chung nhất vì phương thức đó được định nghĩa cho toàn bộ không gian kiểu. Nhưng khi bạn nhập hai số nguyên, trình biên dịch sẽ phân phối về phương thức cụ thể `f(::Int64, ::Int64)`. Ta có thể kiểm tra trong Julia như sau:

```julia
julia> f("string", 5)
any & any

julia> f(4, 5)
int & int
```

<!-- From a visual perspective, we have created an overlapping dispatch, where one method is specifically defined for the integer case f(::Int64, ::Int64) and will be called when only integers are used as arguments. -->

Từ góc nhìn trực quan, chúng ta đã tạo ra một cơ chế phân phối chồng lấp (overlapping dispatch), trong đó có một phương thức được định nghĩa riêng cho trường hợp số nguyên `f(::Int64, ::Int64)` và được phân phối đến chỉ khi các đối số của hàm là các số nguyên.

<div style="text-align: center; margin-top: 50px; margin-bottom: 50px;">
    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681976623361/6e67812f-5ae8-44a5-9d14-a26b8bffe6f8.png" width=100% style="max-width: 100%; max-height: 200px; width: auto; height: auto;">
</div>

<!-- There are some caveats here. If you are not careful the methods can become ambiguous and Julia won't like that. For example if you define the following: -->

## Các phương thức nhập nhằng

Có một số điểm cần lưu ý ở đây. Nếu bạn không cẩn thận, các phương thức có thể trở nên nhập nhằng (ambigous) và Julia sẽ báo lỗi. Ví dụ, nếu bạn định nghĩa như sau:

```julia
f(::Any, ::String) = println("any & string")
f(::String, ::Any) = println("string & any")
```

Khi đó hàm `f("string", "string")` sẽ được phân phối cho phương thức nào trong 2 cái trên?

```julia
julia> f("string", 5)
string & any

julia> f(5, "string")
any & string

julia> f("string", "string")
ERROR: MethodError: f(::String, ::String) is ambiguous.  Candidates:
  f(::Any, ::String) in Main at REPL[8]:1
  f(::String, ::Any) in Main at REPL[9]:1
Possible fix, define
  f(::String, ::String)
```

<!-- Yikes, that's impossible! Fortunately there is a fix proposed, by explicitly defining the ambiguous case. Though perhaps you should reconsider what your actual intentions are in this design. The visual representation below hopefully makes the mistake more clear. At first there is confusion because the two dispatches overlap and neither is more specific than the other, but we can fix it by defining a more concrete method in the conflicting area. -->

May mắn là có một cách khắc phục được lỗi đó. Đó là định nghĩa cho trường hợp nhập nhằng. Ban đầu có sự nhầm lẫn vì hai cách dispatch chồng lấp và không cái nào cụ thể hơn cái còn lại. Ta có thể khắc phục bằng cách định nghĩa một phương thức cụ thể hơn trong khu vực xung đột.

<div style="text-align: center; margin-top: 50px; margin-bottom: 50px;">
    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681980801630/59886127-3d95-4bce-aff0-bd436fb2a9b2.png" width=100% style="max-width: 100%; max-height: 200px; width: auto; height: auto;">
</div>

<!-- When you define a lot of methods, you are creating a colorful patchwork in the type space of your function. You can come up with the craziest designs in your methods, but be careful. Finding the right balance of a few big broad abstract methods versus multiple tiny concrete methods is a true art in Julia. -->

Khi bạn định nghĩa nhiều phương thức, bạn đang tạo ra một bức tranh đầy màu sắc trong không gian kiểu của hàm. Bạn có thể sáng tạo ra những thiết kế độc đáo nhất trong các phương thức của mình, nhưng hãy cẩn thận. Tìm được sự cân bằng hợp lí giữa một vài phương thức trừu tượng lớn và rộng so với nhiều phương thức cụ thể nhỏ là một nghệ thuật thực sự trong Julia.

<div style="text-align: center; margin-top: 50px; margin-bottom: 50px;">
    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681976654572/db41ed23-bc30-41aa-8370-23ad2ebe75fd.png" width=100% style="max-width: 100%; max-height: 200px; width: auto; height: auto;">
</div>

<!-- People do not often share how they visualize the code design in their mind, while I believe this really shapes the creative process. The closest visual representation in Julia I have seen is the article about [Julia's dispatch with Pokemon types](https://www.moll.dev/projects/effective-multi-dispatch/). You can read that for more detailed examples with Julia's multiple dispatch. -->

Mọi người thường không chia sẻ cách họ hình dung thiết kế mã (code) trong tâm trí, trong khi tôi tin rằng điều này thực sự định hình quá trình sáng tạo. Hình ảnh gần nhất với thiết kế mã trong Julia mà tôi đã thấy là bài viết về [cách dispatch của Julia với các loại Pokemon](https://www.moll.dev/projects/effective-multi-dispatch/). Bạn có thể đọc bài viết đó để xem các ví dụ chi tiết hơn về cơ chế dispatch đa chiều của Julia.

## Tham khảo
- [https://scientificcoder.com/the-art-of-multiple-dispatch](https://scientificcoder.com/the-art-of-multiple-dispatch)

