淺談Julia語言:Julia的面向對象

淺談Julia語言:Julia的面向對象

來自專欄 Half Integer21 人贊了文章

Julia語言將在今年8月6日發布1.0版本,我相信很多一直在觀望的人也已經躍躍欲試了。這個系列的文章將結合我在開發Yao的過程中所實際感受到的一些問題和經驗來談談Julia語言。因為並非PL背景,我不會從語言設計上去介紹太多細節,一切從實際使用感受出發。

Julia語言的OO是與其它語言完全不同的。Julia是基於多重派發和類型系統的OO。這一套系統設計地也很巧妙,所以也有人評價說,Julia的性能很大程度是語言設計帶來的。

多態

Julia沒有class,但是在Julia里你也可以認為一切都是object,而這些object都是某個類型的實例。一個Julia的複合類型(Composite Type)可以這樣聲明,幾乎和C是一樣的。而實際上在Julia里類型分為複合類型(Composite Type),基礎類型(Primitive Type)等等,本文不會介紹基礎語法,還請參考官方文檔(英文)。

struct Kitty name::Stringend

那麼這樣Julia就不能像Python/C++等語言一樣去通過讓某個method屬於一個class來實現多態。因為類型除了自己的constructor以外是不允許含有其它方法的。Julia使用了多重派發來解決這個問題。什麼是多重派發?可以參見我另外一篇文章:PyTorch源碼淺析(五)

於是在Julia里,method便是類型(type)和類型之間的相互作用(interaction),而非類(class)對其它類之間的作用。對於傳統的OOP的優缺點在知乎上已經有過很多討論了,在數學,物理等科學計算領域,我們往往需要定義很多複雜的關係,在概念上這樣的方式更加直接,OOP在很多科學計算的場景下並不是很合適。Julia這種基於多重派發和類型的OO不妨是一種更加合適的嘗試。例如,一個浮點數和整數的加法

+(lhs::Int32, res::Float64) = # ...

這個加法並不屬於Int類型也不屬於Float,這在數學上很講得通。總體來講,用Julia為理論對象進行抽象會非常自然。

然後如果你使用jupyter notebook就還會發現,由於method不再屬於一個class,方法和類型之間的耦合更小。你可以先定義一些方法,然後在cell運行一下,然後再定義一些方法,而不需要再class中將所有的方法都聲明完。

類型系統

僅僅有多重派發只能提供一些多態,但是無法實現類似繼承的功能。這一點由類型系統來完成,但是請切記,不要將傳統OOP里繼承的思想搬過來,這是我接觸地很多Julia的初學者,尤其是從Python/C++轉來的初學者會發生的錯誤。這樣的代碼很不Julian,因為語言本身並沒有繼承的概念而且你會發現最後會導致自己手動複製代碼從而造成大量的重複代碼。當然如果你想去寫類似OOP的代碼風格的Julia,當然是可以做到的,但我不建議這麼做。

首先簡要回顧一下類型系統。Julia的類型系統是由抽象類型和實際類型(Concrete Type)構成的類型樹。子類型會獲得父類型行為,而不會獲得父類型的成員。所以Julia是鴨子類型(Duck Type)的。在文檔中,Julia Team強調說:我們更加在意類型的行為,而不是它的成員,所以無法繼承成員是設計成這樣的。

很多人都問過我,那麼如果我有一些公共的成員需要各個子類型都有怎麼辦?如何少些重複代碼?下面我來講幾種方案,請針對自己的情況去選擇

  1. 公共的成員是一些靜態成員(不是type trait)

定義共享的行為,而不是共享的成員

abstract type A endstruct B <: A endstruct C <: A endname(::A) = "no name" # 默認沒有名字name(::B) = "name B" # B 是另外的名字,而C就繼承了A的行為

2. 成員是完全一樣的,但是行為有所不同

使用Symbol作為標籤來分發不同的行為,但是它們共享一個參數類型。

struct A{Tag} name::Stringendname(x::A{:boy}) = "the boys name is $(x.name)"name(x::A{:girl}) = "the girls name is $(x.name)"

3. 成員不同,部分是公共的,並且不是靜態的

這種情況下,我們依然是通過行為去定義類的結構。我們需要有一個公共的method作為interface,這樣我們就不必去管類里具體的結構。雖然不可避免地你需要用一個新的類型封裝一下公共的成員,或者你需要手寫一遍。

struct A m1 m2 nameendname(x::A) = x.namestruct B m1 nameendname(x::B) = x.name

所以使用類型的時候,我們不鼓勵通過 . 來調用類型成員,我們鼓勵去調用某個method,也就是使用類型的行為。不過實際上在具體實現的時候,通過合理地解耦,你會發現第三種情況實際上出現地相對較少,更多出現的是一二兩種情況。如果你遇到了第三種情況不妨再思考思考。

以上經驗,總結一下就是:在Julia里行為(behaviour)比其它的事情更加重要。而類型僅僅是用來派發行為的一種標籤。


推薦閱讀:

TAG:編程語言 | 編程 | Julia編程語言 |