依型別分派(dispatch on types)
依型別分派(dispatch on types)算是在 Julia 語言中相當常見的一個技巧,它依賴 Julia 的多重分派機制(multiple dispatch),可以依據不同的型別有不一樣的行為。
依據不同的型別有不同的行為,而不是實體。直觀上看起來會有點像傳統物件導向當中的 class method,不過在 Julia 當中還有更多用途。
Convert
在 Julia 的 Base 中有許多應用到這樣技巧的例子,這邊就舉 convert
為例。
convert
是一個轉換函式,它可以將特定物件轉換成特定的型別,所以物件跟型別就分別是它的參數。
1 | convert(Char, 10) |
這樣可以將 10
轉換成一個字元的型別,就會變成 '\n'
。
1 | convert(Array{Float64}, Any[1 2 3; 4 5 6]) |
或是一個將裝有 Any
型別元素的矩陣,轉成 Float64
型別元素的矩陣。
以上的實作大概會類似:
1 | function convert(T::Type{Char}, val) |
基本上會試圖呼叫該型別的建構子,而第一個參數上也會加上 Type{...}
的型別。
Read
或是我們也可以在讀取二進制資料當中發現這樣的模式。
1 | io = IOBuffer("JuliaLang is the best.") |
在讀取 IOBuffer
的二進制資料當中,可以將資料解析成 Char
的型別。
1 | read(io, String) |
或是解析成 String
的型別。
在這邊我們看到的都是將型別作為參數給進函式中,函式可以藉由得到的型別做不同的事情。
其中一個目的是依據型別做分派(dispatch),也就是不同的型別會對應到不同的方法實作上。
read(io, Char)
跟 read(io, String)
的實作方式是截然不同的,畢竟要解析成不同的型別,方法會是不同的。
也有可能會像是 convert
一樣,再度利用給進來的參數。由於 Julia 的型別本身也是一個物件,呼叫型別本身也等同於呼叫型別的建構子,所以我們可以看到在 convert(T::Type{Char}, val)
中,呼叫 T
作為建構子的方式來轉換物件的型別。
用在哪裡?
這樣的技術可以被用在哪些場景呢?
通常需要傳型別到其它函式,可以用來呼叫其建構子,而函式則提供一個統一的介面,可以適用於創造物件的場景。
以下就來示範有不同種飲料被製作出來的過程吧!
1 | abstract type Beverage end |
這邊定義了奶茶、綠茶及紅茶幾種飲料,然後還可以對飲料加料,加料之後的飲料就會變成其他種的飲料。
接下來就可以來決定加什麼料會變成什麼樣的飲料。
1 | mix(::Type{MilkTea}, ::Type{TapiocaBall}) = BubbleMilkTea() |
像是把奶茶跟波霸加在一起,就變成了波霸奶茶。
1 | mix(::Type{MilkTea}, ::Type{Pudding}) = PuddingMilkTea() |
如果把奶茶跟布丁加在一起就變成布丁奶茶囉~~
1 | mix(::Type{GreenTea}, ::Type{TapiocaBall}) = BubbleGreenTea() |
我們還有賣波霸綠茶跟波霸紅茶喔!
但是有些組合沒有在菜單上,因為老闆覺得沒有在菜單上的組合喝起來很噁心,所以不打算提供,像是布丁加綠茶這種組合。
1 | julia> mix(GreenTea, Pudding) |
在這邊 mix
就提供了一個統一的介面來混合飲料跟加料的部份。
提供這樣單一的物件創造介面就類似於物件導向中的 factory method pattern。
依參數型別分派(2021.5.24 補充)
如果有個參數化型別 Foo
,它帶有一個型別參數 K
。
1 | julia> struct Foo{K} end |
如果有個函式 foo
想依據不同 K
分派,例如,當 K=1
時,可以回傳一個 "BOOM!"
,否則就回傳 K
自己。
1 | julia> foo(::Foo{K}) where K = K |
這樣會發現我們也可以依據 K
的值分派到不同的函式。
1 | julia> foo(Foo{1}()) |
總結
我們可以發現到使用多重分派所帶來的一些好處。如果是在單一分派(single dispatch),也就是一般物件導向的語言中,他只能依據第一個參數做分派。然而,多重分派就可以考慮參數型別的排列組合去做分派,當然參數是型別也是可行的。
類似這樣的機制,也可以對應到在物件導向中的 strategy pattern,strategy pattern 是根據不同的演算法種類來做分派的,我們可以寫成像這樣:
1 | sort(BubbleSort, xs) |
如此一來,就是一個完整的 strategy pattern 了。
最後,這邊介紹了如何利用多重分派的機制,來依據不同的型別做分派,並且做到不同的應用。