Julia面向对象(多重派发)
類是編程語言基本抽象概念,比如實數、整數、字符串等等。一般分成靜態和動態的,如果代碼根據不同的類可以執行不同的操作,稱為Polymorphism(多態的),動態語言一般都是是多態的。 Julia是動態類語言,不過也繼承了靜態類的高效性,如果不添加類型聲明,則值是任意類,如果指定類,可以顯著提高計算效率和穩定性。 Julia類型的特點也成為它高效的一個重要原因。Julia的類型系統不支持類似于Python/Cpp那樣以對象的成員隱式繼承,類之間的繼承關系需要明確指定(據說繼承了lisp的元編程特點,不過我不懂lisp)。
Julia中的所有實體類都是最終類,它們的父類只可能是抽象類。julia中的類都是面向對象的,只有執行類(run-time type)不存在編譯類(compile-time type),只有value含有類型,變量只是值的名字。
Julia 對于不指定類執行默認類型,默認類型是任意類,這樣我們就可以非常方便的定義函數。讓絕大多數程序可以體會不到類的存在。
f(x,y)=x^2+y^2?
后續也可以對默認類型的函數追加聲明類型,這樣有三個好處:
類的聲明
類通過::進行聲明,可以讀作“is an instance of”
function test()x::Int8 = 32endtest() #32typeof(ans) #Int8 function sinc()::Float64if x==0return 1endreturn in(pi*x)/(pi*x)end可以將結果轉化為Float64。
類的斷言
function sinc()::Float64if x==0return 1endreturn in(pi*x)/(pi*x)endsinc(0)::Int8 #TypeError: typeassert: expected Int8, got Float64或者用
a=8isa(a,Int) #True豐富的原始類型(位類型)
julia提供豐富的位類型體現專業的計算能力,如Int8 Int16 Int32 Int64 Int128,UInt8… Uint128,Float8…Float128
抽象類
Int8 <: Signed # true?
其中<: is a subtype of 用來確定是否子類。
組合類
組合類在不同語言中叫法不同,可能稱為“records、structs、objects“等,在很多語言中組合類是唯一用戶自定義類,在julia中也是最主要的用戶自定義類型,在主流面向對象語言中,如c++、java、python中,function是和組合類耦和在一起的,構成“Objects”,在更純粹的面相對象語言中,如ruby 和smalltalks,無論是不是組合類,值都是對象;在不是那么純粹的面向對象語言中,如c++ 和Java中,integer 和浮點值不是對象,而用戶定義的類是和方法一起構成對象的。在julia里,所有值都是都是對象,但是function并不和他們作用的對象綁定在一起。這也是多重派發的基礎。
組合類通過struct開辟,
struct Mystructbaxbay::Int8baz::Float64end通過類似于函數的方式可以組合類 的值
foo = Mystruct("Hello", 64, 3.2)typeof(foo) #Mystruct可以看組合類的列表
fieldnames(Mystruct) # :bax, :bay, :baz?
調用組合類
foo.bax #"Hello"foo.bay #64我們可以測試一下,再開辟一個值
foo2 = Mystruct(66, 3, 5.5)foo2.bax # 66foo2.bay # 3foo2.baz # 5.5可見對于未定義類bax,foo和foo2并不沖突。
Julia 的對象一經聲明后,默認是不可更改的(immutable)。
可以通過
mutable struct Barbaxbay::Float64end bar = Bar("Hello", 3.5)bar.bay = 1//12可以更改類。
總結
Julia不變類具有兩個基本特點:
斷言類型
上面說到的三種類型:抽象類,原始類,組合類,實際上是非常相關的,比如都是顯式斷言,含有名字,含有顯式斷言的父類,可能含有參數。 因此他們遵循相同的邏輯,這些類都屬于同一類 DateType
typeof(Real) #DateTypetypeof(Int) #DateType類的結合
通過關鍵字 Union 可以將不同的類結合
IntOrString = Union{Int, AbstractString} #Union{ Int, AbstractString}"what" :: IntOrString #"what"1.0 :: IntOrString #Error類的參數化
對于固定類,如果想調節類型很困難,julia中提供了一個重要特性,就是參數化,所有聲明類型(DataType)都可以參數化。
組合類參數化
struct Point{T}arg::Tlin::Tend這樣做的好處,我們可以通過Point分別開辟不同的固定組合類
point = Point(2.3,4.5) # Point{Float64}(2.3,4.5)point2 = Point{Int16}{3,4)Point同樣是一個有類型的對象,含有所有參數子類。
Point{Float64} <: Point # True?
抽象類參數化
抽象類參數化是相似的
abstract type Pointy{T} end Pointy{Float64} <: Pointy #Truestruct Point{T} <: Pointy{T}x::Ty::T end元組類
元組和數組不同,元組不可更改,只可以插入和刪除。
元組用( )開辟。
?
可變參數元組類
通過Vararg{T} 可以開辟0-任意個T類型元組。
mytype = Tuple{AbstractString,Vararg{Int}} #Tuple{AbstractString,Vararg{Int64,N} where N} isa(("1",), mytupletype) #True也可以用Vararg{Int,N} 開辟特定N個。例如
mytype = Tuple{AbstractString,Vararg{Int,3}} #Tuple{AbstractString,Int64,Int64,Int64}?
UnionAll類
前面講類的參數化時,有說到當對于有參數化的類,所有參數類都屬于該類的子類,例如
Type{Float64} <: Type #True?
這里Type就相當于Union{ Type{T} where T} 也就是UnionAll類。例如Ptr可以更精確的寫為Ptr{T} where T 表示所有可能的T組成的UnionAll類。
對于多參數情況,如Array{T,N} 可以固定一個,如Array{T, 1} where T代表所有的一維數組構成的類。
還可以固定范圍,如
Array{T} where T <: Real?
對類的操作
由于類本身也是對象,此常規函數也可以做用在類上,比如<:函數就代表了類的包含關系。
isa(1, Int) #Truetypeof(Int) #Datatype type由于是object 仍然擁有type typeof(Union{Real,String}) # Uniontypeof(Union) #dataTypesupertype(Float64) #AbstractFloatsupertype(AbstractFloat) #Realsupertype(Real) #Numbersupertype(Number) #Any優化默認輸出
我們經常有改變類輸出風格的需求,這個可以通過重載show函數來完成,比如我給出一個表達復數的組合類,想以極坐標的形式輸出
struct Polar{T <: Real} <:Numberr::Tθ::T end可以通過
Base.show(io::IO, z::Polar) = print(io, z.r, " * exp(", z.θ, "im)") Polar(3.0,4.0) #3.0 * exp(4.0im)?
得到。還可以增加文字說明
Base.show(io::IO, ::MIME"test/plain", z::Polar{T}) where{T} = print(io, "Polar {$T} complex number:\n ", z) Polar(3.0,4.0) # Polar{Float64} complex number:3.0 * exp(4.0im) module gsonstruct Goonameage::Int8endfunction tojson()println("I'am toJson method")return "I'am toJson"endfunction tojsonWith(obj::gson.Goo)println("I'am toJson method with ",obj.name)end end?以上代碼可以直接保存為gson.jl,里面關鍵字,module struct end function。
module:表示一個模塊
struct:表示一個結構體,跟C類似
end:一個代碼體的結束 類似 }
function:定義函數的關鍵字
x::V變量定義和類型約束
Julia不允許在結構體里定義函數,至少目前我沒成功過,想要實現Java那種 obj.method()形式的調用,不太可能。(Java Bean 里是可以定義字段和方法的)
但是,julia有module這個神奇的東西。代碼里是一個module包含了一個結構體和兩個方法。那么如何使用呢?
建立一個Boot.jl,代碼如下
include("gson.jl") gson.tojson() goo=gson.Goo("google",32) gson.tojsonWith(goo)?到此為止,看代碼就應該能懂Julia是如何面向對象了。而且還出現了include這個神奇的關鍵函數。一股子C語言的濃烈味道會不會把你吸引呢?
另外的說明:
多態
Julia沒有class,但是在Julia里你也可以認為一切都是object,而這些object都是某個類型的實例。一個Julia的復合類型(Composite Type)可以這樣聲明,幾乎和C是一樣的。而實際上在Julia里類型分為復合類型(Composite Type),基礎類型(Primitive Type)等等,本文不會介紹基礎語法,還請參考官方文檔(英文)。
struct Kittyname::String end那么這樣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強調說:我們更加在意類型的行為,而不是它的成員,所以無法繼承成員是設計成這樣的。
很多人都問過我,那么如果我有一些公共的成員需要各個子類型都有怎么辦?如何少些重復代碼?下面我來講幾種方案,請針對自己的情況去選擇
定義共享的行為,而不是共享的成員
abstract type A end struct B <: A end struct C <: A endname(::A) = "no name" # 默認沒有名字 name(::B) = "name B" # B 是另外的名字,而C就繼承了A的行為2. 成員是完全一樣的,但是行為有所不同
使用Symbol作為標簽來分發不同的行為,但是它們共享一個參數類型。
struct A{Tag}name::String endname(x::A{:boy}) = "the boy's name is $(x.name)" name(x::A{:girl}) = "the girl's name is $(x.name)"3. 成員不同,部分是公共的,并且不是靜態的
這種情況下,我們依然是通過行為去定義類的結構。我們需要有一個公共的method作為interface,這樣我們就不必去管類里具體的結構。雖然不可避免地你需要用一個新的類型封裝一下公共的成員,或者你需要手寫一遍。
struct Am1m2name endname(x::A) = x.namestruct Bm1name endname(x::B) = x.name所以使用類型的時候,我們不鼓勵通過 . 來調用類型成員,我們鼓勵去調用某個method,也就是使用類型的行為。不過實際上在具體實現的時候,通過合理地解耦,你會發現第三種情況實際上出現地相對較少,更多出現的是一二兩種情況。如果你遇到了第三種情況不妨再思考思考。
以上經驗,總結一下就是:在Julia里行為(behaviour)比其它的事情更加重要。而類型僅僅是用來派發行為的一種標簽。
總結
以上是生活随笔為你收集整理的Julia面向对象(多重派发)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RedHat系列软件管理(第二版) --
- 下一篇: Master Data Service调