可組合性(Composable):Julia 語言的重要特性
其實是受到 Julia 核心開發者之一 Lyndon White 的文章的啟發來撰寫本文的。
文章有 CSDNnews 簡中翻譯。
本文並非以上文章的繁中版本,而是本人的一些心得及觀察。
載入套件
比較深入了解 Julia 的朋友,會了解到這個語言的設計與其他語言的不同,像是 using
。
當我們使用 using
來載入套件的時候,會發現有不少存在在 Base
中的函式是可以使用的。像是 length
可以用來取得陣列的長度,當你載入 DataStructures.jl 時,你同樣可以用 length
來取得 Stack
及 Queue
的長度。
或者是原文中的例子。使用者可以使用 NamedDims.jl 來為你的陣列的維度命名,而你的陣列需要使用 CuArrays.jl 送到 CUDA,這時候你不需要一個可以為 CUDA array 命名的套件來達成這件事。
你會發現在 Julia 語言中似乎不存在套件跟套件之間的差別,或是套件跟標準函式庫之間的區別。這是由於在 Julia 中對於命名空間(namespace)的概念較為薄弱,Julia 各模組之間仍然存在著命名空間,但是 Julia 在 using
時會將這些命名空間去除。這在工程上或許不是一個好的典範,因為會造成命名空間的汙染(namespace pollution)。反過來說,它促使人們相互溝通及協調,來提供更好的套件之間的可組合性。
在其他語言中,常常會告訴你,使用某個套件只需要載入你需要的部份,像是 using Foo: bar, baz
,然而 Julia 並不去特別強調這點,using Foo
會載入套件開發者有導出的部份。如果有兩個套件都提供了 predict
來支援他們的模型,這邊借原文的例子再次說明:
1 | using Foo |
這兩個 predict
分別來自雙方套件的各自定義及實作。
1 | Bar.predict(mbar) |
如此使用者便可以無縫地使用同樣一個介面 predict
,而功能來自兩個不同的套件。
這樣的特性大概會有人聯想到介面(interface)。然而,Julia 並不明顯使用介面來規範開發者,Julia 隱含地使用鴨子定型(duck typing)。
在載入套件之後,Julia 允許使用者不冠上套件名稱來使用這些函式,像 Bar.predict
,你可以自由地使用 predict
即可。總是會有套件之間的命名衝突,當衝突發生的時候,就是開發者需要負起責任彼此溝通及協調的時候。不過使用者仍然可以以 Bar.predict
的方式使用套件,只是每次載入套件的時候都會有警告。
1 | julia> using Plots |
鴨子定型及多重分派
在原文中提到鴨子定型及多重分派是成就 Julia 成為一個可組合(composable)語言的基石。我個人也認為可組合是 Julia 提供最重要的特性之一,但卻鮮少被人提及。多數人仍然熱衷於語言效能及開發便利性。
鴨子定型的一個很好的比喻是,當一個東西會呱呱叫的時候,那麼他就是鴨子。當一個 bar(x)
可以呼叫時,我不需要去檢查 x
的型別為何,我就直接使用就是了。這樣會構成一種隱性的介面。
當我需要提供一個矩陣相乘的功能時,我會寫以下程式碼:
1 | function multiply(A, B) |
這可以用在一般的矩陣,但如果今天我有自定義的一個新的矩陣呢?
1 | struct NewArray |
在上述的 multiply
中我只需要檢查 NewArray
是否提供相關的介面即可。像是裡頭用到了 size
、A[:, k]
及 sum
。我只需要支援這些介面的實作即可,如此,Julia 就可以以鴨子定型的方式,讓 multiply
接受 NewArray
了。
對於不同的型別需要有不同的行為,這在一般的物件導向語言中稱之為多型。多型在多數物件導向典範中使用的是單一分派(single dispatch),然而 Julia 也支援多型,但是以多重分派(multiple dispatch)的方式支援。因此,廣義而言,Julia 支援物件導向的方式是多重分派,卻不是典型的、語法上的封裝、繼承及(單一分派)多型。
多重分派,可以在需要更細緻行為定義時幫上忙。當 NewArray
支援 sum
:
1 | sum(::NewArray) = ... |
這是單一分派的方式,也可以有多重分派的方式。
1 | sum(::NewArray, ::Array) = ... |
不過這樣只支援 Array
這個特定型別。或是我們可以乾脆這樣做。
1 | sum(::NewArray, ::AbstractArray) = ... |
這樣只要是 AbstractArray
的子型別都可以接受。
總結
以上種種的特性成為了可組合性的基石。一個具有可組合性的語言能夠成為各種東西。Julia 的個別的套件都不俱備所有的定義,需要依賴 Julia 語言及標準函式庫的介面及實作。
例如當 Julia 中載入了深度學習框架,那它,連同語言本身,就是一個完整支援深度學習功能的引擎。當同時載入了資料庫與深度學習相關套件,那它就成為了支援深度學習功能的 DBMS。當載入了科學計算及機器學習套件,那它就會變成一個強大的數值計算引擎。以 Julia 的可組合性出發,來打造各式各樣不同的引擎,就像一個單純的編輯器搭配有豐富的外掛(plug-in)一樣。這樣衍生出的生態帶來了各式各樣不同的可能性,也讓套件的 reusability 提升到最高的境界。