依函式分派(dispatch on function)算是在 Julia 語言中相當常見的一個技巧,它依賴 Julia 的多重分派機制(multiple dispatch),可以依據不同的函式有不一樣的行為。

舉例

在資料分析或是資料庫分析中,常常會需要對資料欄位進行聚合運算,像是加總的 sum、平均的 avg 或是計數的 count。他們都是用同一個欄位的資料,聚合成一個值輸出。當我們需要將資料相加時,我們會寫出以下的程式碼:

1
2
a = [1,2,3,4,5]
sum(a)

這看起來很直觀,但往往在資料庫的設計當中,只會設計一些常用到的聚合函式(aggregate function)。

那麼有沒有一個方式可以得到一個比較廣義的聚合函式呢?

假設我們想要定一個函式叫作 aggregate,其實 sum 就是利用加法(+)來做聚合,所以我們可以寫成這樣:

1
2
3
4
5
6
7
function aggregate(op, data)
y = data[1]
for i in 2:length(data)
y = op(y, data[i])
end
y
end

如此一來,下面兩者的結果會一樣。

1
aggregate(+, a) == sum(a)

那如果我們需要一個將所有資料相乘(*)的聚合方式,那我們只需要改寫成以下的方式。

1
aggregate(*, a) == prod(a)

這樣感覺超棒的!

不過這是一般函數式程式設計的寫法,這跟 reduce(*, data) 沒什麼兩樣。

用分派來處理特例

接著,再來實作一個簡單的 aggregate(count, data),如果你直接呼叫 aggregate(count, a) 是會出錯的。

然而 count 的計算其實是需要重新實作的,所以就不能再走上面的方式了。

要依據參數的不同,執行不同的方法就是多型(polymorphism)的真諦。

1
2
3
4
5
6
7
function aggregate(op::typeof(count), data)
y = 1
for i in 2:length(data)
y += 1
end
y
end

或是直接取資料的長度也可以。

1
aggregate(op::typeof(count), data) = length(data)

統計常用函式

這時候如果我們要實作的是一些統計上常用的函式,像是平均、中位數或眾數,就遇到困難了。這邊示範平均的聚合函式,如果像以下的方式寫會發生錯誤:

1
aggregate(mean, a) != mean(a)

因為在這樣的計算之下,它會變成第一項先跟第二項取平均,再跟第三項取平均,再跟第四項取平均,以此類推。

這時候就要將 mean 的實作分開定義。

由於 mean 的行為可以想成是先將資料加總,然後除以資料的數量,所以我們可以計算 aggregate(+, data)aggregate(count, data),並且將他們兩者相除。

1
2
3
function aggregate(op::typeof(mean), data)
aggregate(+, data) / aggregate(count, data)
end

如此一來,就可以依據不同的函式名稱來做分派了!

原理

由於在 Julia 中萬物都是物件,即使是函式也如同物件一樣是有型別的,可以取 typeof,而函式 foo 的型別就是 typeof(foo)

因此,可以將函式 foo 視為實體,函式型別 typeof(foo) 視為型別。

利用一般的多重分派就可以達成 aggregate(op::typeof(mean), data) 囉!