Julia 類型

2021-03-06 14:25 更新

類型

Julia 中,如果類型被省略,則值可以是任意類型。添加類型會(huì)顯著提高性能和系統(tǒng)穩(wěn)定性。

Julia 類型系統(tǒng)的特性是,具體類型不能作為具體類型的子類型,所有的具體類型都是最終的,它們可以擁有抽象類型作為父類型。其它高級(jí)特性有:

  • 不區(qū)分對(duì)象和非對(duì)象值:Julia 中的所有值都是一個(gè)有類型的對(duì)象,這個(gè)類型屬于一個(gè)單一、全連通類型圖,圖中的每個(gè)節(jié)點(diǎn)都是類型。
  • 沒(méi)有“編譯時(shí)類型”:程序運(yùn)行時(shí)僅有其實(shí)際類型,這在面向?qū)ο缶幊陶Z(yǔ)言中被稱為“運(yùn)行時(shí)類型”。
  • 值有類型,變量沒(méi)有類型——變量?jī)H僅是綁定了值的名字而已。
  • 抽象類型和具體類型都可以被其它類型和值參數(shù)化。具體來(lái)講, 參數(shù)化可以是 符號(hào), 可以是 isbits 返回值為 true 的類型任意值 (本質(zhì)想是講, 這些數(shù) 像整數(shù)或者布爾值一樣, 儲(chǔ)存形式類似于 C 中的數(shù)據(jù)類型或者 struct, 并且 沒(méi)有指向其他數(shù)據(jù)的指針), 也可以是元組。如果類型參數(shù)不需要被使用或者 限制, 可以省略不寫。

Julia 的類型系統(tǒng)的設(shè)計(jì)旨在有效及具表現(xiàn)力,既清楚直觀又不夸張。許多 Julia 程序員可能永遠(yuǎn)不會(huì)覺(jué)得有必要去明確地指出類型。然而某些程序會(huì)因聲明類型變得更清晰,更簡(jiǎn)單,更迅速及健壯。

類型聲明

:: 運(yùn)算符可以用來(lái)在程序中給表達(dá)式和變量附加類型注釋。這樣做有兩個(gè)理由:

  1. 作為斷言,幫助確認(rèn)程序是否正常運(yùn)行
  2. 給編譯器提供額外類型信息,幫助提升性能

:: 運(yùn)算符放在表示值的表達(dá)式之后時(shí)讀作“前者是后者的實(shí)例”,它用來(lái)斷言左側(cè)表達(dá)式是否為右側(cè)表達(dá)式的實(shí)例。如果右側(cè)是具體類型,此類型應(yīng)該是左側(cè)的實(shí)例。如果右側(cè)是抽象類型,左側(cè)應(yīng)是一個(gè)具體類型的實(shí)例的值,該具體類型是這個(gè)抽象類型的子類型。如果類型斷言為假,將拋出異常,否則,返回左值:

    julia> (1+2)::FloatingPoint
    ERROR: type: typeassert: expected FloatingPoint, got Int64

    julia> (1+2)::Int
    3

可以在任何表達(dá)式的所在位置做類型斷言。 :: 最常見(jiàn)的用法是作為一個(gè)在函數(shù)/方法簽名中的斷言,例如 f(x::Int8) = ... (查看方法)。

:: 運(yùn)算符跟在表達(dá)式上下文中的變量名后時(shí),它聲明變量應(yīng)該是某個(gè)類型,有點(diǎn)兒類似于 C 等靜態(tài)語(yǔ)言中的類型聲明。賦給這個(gè)變量的值會(huì)被 convert 函數(shù)轉(zhuǎn)換為所聲明的類型:

    julia> function foo()
             x::Int8 = 1000
             x
           end
    foo (generic function with 1 method)

    julia> foo()
    -24

    julia> typeof(ans)
    Int8

這個(gè)特性用于避免性能陷阱,即給一個(gè)變量賦值時(shí)意外更改了類型。

“聲明”僅發(fā)生在特定的上下文中:

    x::Int8        # a variable by itself
    local x::Int8  # in a local declaration
    x::Int8 = 10   # as the left-hand side of an assignment

并適用于整個(gè)當(dāng)前范圍,甚至在聲明之前。目前,聲明類型不能用于全局范圍,例如在 REPL 中就不可以,因?yàn)?Julia 還沒(méi)有定型的全局變量。需要注意的是在函數(shù)返回語(yǔ)句中,上述的前兩個(gè)表達(dá)式計(jì)算值,還有就是 :: 是一個(gè)類型的斷言不是一個(gè)聲明。

抽象類型

抽象類型不能被實(shí)例化,它組織了類型等級(jí)關(guān)系,方便程序員編程。如,編程時(shí)可針對(duì)任意整數(shù)類型,而不需指明是哪種具體的整數(shù)類型。

使用 abstract 關(guān)鍵字聲明抽象類型:

    abstract ?name?
    abstract ?name? <: ?supertype?

abstract 關(guān)鍵字引入了新的抽象類型,類型名為 ?name? 。類型名后可跟 <: 及已存在的類型,表明新聲明的抽象類型是這個(gè)“父”類型的子類型。

如果沒(méi)有指明父類型,則父類型默認(rèn)為 Any ——所有對(duì)象和類型都是這個(gè)抽象類型的子類型。在類型理論中,Any 位于類型圖的頂峰,被稱為“頂”。Julia 也有預(yù)定義的抽象“底”類型,它位于類型圖的最底處,被稱為 None。NoneAny 對(duì)立:任何對(duì)象都不是 None 的實(shí)例,所有的類型都是 None 的父類型。

下面是構(gòu)造 Julia 數(shù)值體系的抽象類型子集的具體例子:

    abstract Number end
    abstract Real     <: Number
    abstract AbstractFloat <: Real
    abstract Integer  <: Real
    abstract Signed   <: Integer
    abstract Unsigned <: Integer

<: 運(yùn)算符意思為“前者是后者的子類型”,它聲明右側(cè)是左側(cè)新聲明類型的直接父類型。也可以用來(lái)判斷左側(cè)是不是右側(cè)的子類型:

    julia> Integer <: Number
    true

    julia> Integer <: FloatingPoint
    false

抽象類型的一個(gè)重要用途是為具體的類型提供默認(rèn)實(shí)現(xiàn)。舉個(gè)簡(jiǎn)單的例子:

  function myplus(x, y)
      x + y
  endof

第一點(diǎn)需要注意的是, 上面的參數(shù)聲明等效于 x::Anyy::Any. 當(dāng)這個(gè)函數(shù)被調(diào)用時(shí), 例如 myplus(2, 5), Julia 會(huì)首先查找參數(shù)類型匹配的 myplus 函數(shù). (關(guān)于多重分派的詳細(xì)信息請(qǐng)參考下文.) 如果沒(méi)有找到比上面的函數(shù)更相關(guān)的函數(shù), Julia 根據(jù)上面的通用函數(shù)定義并編譯一個(gè) myplus 具體函數(shù), 其參數(shù)為兩個(gè) Int 型變量, 也就是說(shuō), Julia 會(huì)定義并編譯:

  function myplus(x::Int, y::Int)
      x + y
  end

最后, 調(diào)用這個(gè)具體的函數(shù)。

因此, 程序員可以利用抽象類型編寫通用的函數(shù),然后這個(gè)通用函數(shù)可以被許多具體的類型組合調(diào)用。也正是由于多重分派,程序員可以精確的控制是調(diào)用更具體的還是通用的函數(shù)。

需要注意的一點(diǎn)是, 編寫面向抽象類型的函數(shù)并不會(huì)帶來(lái)性能上的損失,因?yàn)槊看握{(diào)用函數(shù)時(shí),根據(jù)不同的參數(shù)組合,函數(shù)總是要重新編譯的。(然而, 如果參數(shù)類型為包含抽象類型的容器是, 會(huì)有性能方面的問(wèn)題;參見(jiàn)下面的關(guān)于性能的提示。)

位類型

位類型是具體類型,它的數(shù)據(jù)是由位構(gòu)成的。整數(shù)和浮點(diǎn)數(shù)都是位類型。標(biāo)準(zhǔn)的位類型是用 Julia 語(yǔ)言本身定義的:

    bitstype 16 Float16 <: FloatingPoint
    bitstype 32 Float32 <: FloatingPoint
    bitstype 64 Float64 <: FloatingPoint

    bitstype 8  Bool <: Integer
    bitstype 32 Char <: Integer

    bitstype 8  Int8     <: Signed
    bitstype 8  Uint8    <: Unsigned
    bitstype 16 Int16    <: Signed
    bitstype 16 Uint16   <: Unsigned
    bitstype 32 Int32    <: Signed
    bitstype 32 Uint32   <: Unsigned
    bitstype 64 Int64    <: Signed
    bitstype 64 Uint64   <: Unsigned
    bitstype 128 Int128  <: Signed
    bitstype 128 Uint128 <: Unsigned

聲明位類型的通用語(yǔ)法是:

    bitstype ?bits? ?name?
    bitstype ?bits? ?name? <: ?supertype?

?bits? 表明類型需要多少空間來(lái)存儲(chǔ),?name? 為新類型的名字。目前,位類型的聲明的位數(shù)只支持 8 的倍數(shù),因此布爾類型也是 8 位的。

Bool, Int8Uint8 類型的聲明是完全相同的,都占用了 8 位內(nèi)存,但它們是互相獨(dú)立的。

復(fù)合類型

復(fù)合類型也被稱為記錄、結(jié)構(gòu)、或者對(duì)象。復(fù)合類型是變量名域的集合。它是 Julia 中最常用的自定義類型。在 Julia 中,所有的值都是對(duì)象,但函數(shù)并不與它們所操作的對(duì)象綁定。Julia 重載時(shí),根據(jù)函數(shù) 所有參數(shù)的類型,而不僅僅是第一個(gè)參數(shù)的類型,來(lái)選取調(diào)用哪個(gè)方法(詳見(jiàn) :方法 )。

使用 type 關(guān)鍵字來(lái)定義復(fù)合類型:

    julia> type Foo
             bar
             baz::Int
             qux::Float64
           end

構(gòu)建復(fù)合類型 Foo 的對(duì)象:

    julia> foo = Foo("Hello, world.", 23, 1.5)
    Foo("Hello, world.",23,1.5)

    julia> typeof(foo)
    Foo (constructor with 2 methods)

當(dāng)一個(gè)類型像函數(shù)一樣被調(diào)用時(shí),它可以被叫做類型構(gòu)造函數(shù)(constructor)。每個(gè)類型有兩種構(gòu)造函數(shù)是自動(dòng)被生成的(它們被叫做默認(rèn)構(gòu)造函數(shù))。第一種是當(dāng)傳給構(gòu)造函數(shù)的參數(shù)和這個(gè)類型的字段類型不一一匹配時(shí),構(gòu)造函數(shù)會(huì)把它的參數(shù)傳給 convert 函數(shù),并且轉(zhuǎn)換到這個(gè)類型相應(yīng)的字段類型。第二種是當(dāng)傳給構(gòu)造函數(shù)的每個(gè)參數(shù)和這個(gè)類型的字段類型都一一相同時(shí),構(gòu)造函數(shù)直接生成類型。要自動(dòng)生成兩種默認(rèn)構(gòu)造函數(shù)的原因是:為了防止用戶在聲明別的新變量的時(shí)候不小心把構(gòu)造函數(shù)給覆蓋掉。

由于沒(méi)有約束 bar 的類型,它可以被賦任意值;但是 baz 必須能被轉(zhuǎn)換為 Int

    julia> Foo((), 23.5, 1)
    ERROR: InexactError()
     in Foo at no file

你可以用 names 這個(gè)函數(shù)來(lái)獲取類型的所有字段。

    julia> names(foo)
    3-element Array{Symbol,1}:
     :bar
     :baz
     :qux

獲取復(fù)合對(duì)象域的值:

    julia> foo.bar
    "Hello, world."

    julia> foo.baz
    23

    julia> foo.qux
    1.5

修改復(fù)合對(duì)象域的值:

    julia> foo.qux = 2
    2.0

    julia> foo.bar = 1//2
    1//2

沒(méi)有域的復(fù)合類型是單態(tài)類型,這種類型只能有一個(gè)實(shí)例:

    type NoFields
    end

    julia> is(NoFields(), NoFields())
    true

is 函數(shù)驗(yàn)證 NoFields 的“兩個(gè)”實(shí)例是否為同一個(gè)。有關(guān)單態(tài)類型,后面會(huì)詳細(xì)講。

有關(guān)復(fù)合類型如何實(shí)例化,需要 參數(shù)化類型方法這兩個(gè)背景知識(shí)。將在構(gòu)造函數(shù)中詳細(xì)介紹構(gòu)造實(shí)例。

不可變復(fù)合類型

可以使用關(guān)鍵詞 immutable 替代 type 來(lái)定義 不可變 復(fù)合類型:

    immutable Complex
      real::Float64
      imag::Float64
    end

這種類型和其他復(fù)合類型類似,除了它們的實(shí)例不能被更改。不可變復(fù)合類型具有以下幾種優(yōu)勢(shì):

  • 它們?cè)谝恍┣闆r下更高效。像上面 Complex 例子里的類型就被有效地封裝到數(shù)組里,而且有些時(shí)候編譯器能夠避免完整地分配不可變對(duì)象。
  • 不會(huì)與類型的構(gòu)造函數(shù)提供的不變量沖突。
  • 用不可變對(duì)象的代碼不容易被侵入。

一個(gè)不可變對(duì)象可以包含可變對(duì)象,比如數(shù)組,域。那些被包含的可變對(duì)象仍然保持可變;只有不可變對(duì)象自己的域不能變得指向別的對(duì)象。

理解不可變復(fù)合變量的一個(gè)有用的辦法是每個(gè)實(shí)例都是和特定域的值相關(guān)聯(lián)的 - 這些域的值就能告訴你關(guān)于這個(gè)對(duì)象的一切。相反地,一個(gè)可變的對(duì)象就如同一個(gè)小的容器可能包含了各種各樣的值,所以它不能從它的域的值確定出這個(gè)對(duì)象。在決定是否把一個(gè)類型定義為不變的時(shí)候,先問(wèn)問(wèn)是否兩個(gè)實(shí)例包含相同的域的值就被認(rèn)為是相同,或者它們會(huì)獨(dú)立地改變。如果它們被認(rèn)為是相同的,那么這個(gè)類型就該被定義成不可變的。

再次強(qiáng)調(diào)下, Julia 中不可變類型有兩個(gè)重要的特性:

  • 不可變復(fù)合類型的數(shù)據(jù)在傳遞時(shí)會(huì)被拷貝 (在賦值時(shí)是這樣, 在調(diào)用函數(shù)時(shí)也是這樣), 相對(duì)的, 可變類型的數(shù)據(jù)是以引用的方式互相傳遞.
  • 不可變復(fù)合類型內(nèi)的域不可改變.

對(duì)于有著 C/C++ 背景的讀者, 需要仔細(xì)想下為什么這兩個(gè)特性是息息相關(guān)的。設(shè)想下,如果這兩個(gè)特性是分開的,也就是說(shuō),如果數(shù)據(jù)在傳遞時(shí)是拷貝的, 然而數(shù)據(jù)內(nèi)部的變量可以被改變, 那么將很難界定某段代碼的實(shí)際作用。舉個(gè)例子,假設(shè) x 是某個(gè)函數(shù)的參數(shù), 同時(shí)假設(shè)函數(shù)改變了參數(shù)中的一個(gè)域:x.isprocessed = true。根據(jù) x 是值傳遞或者引用傳遞, 在調(diào)用完函數(shù)是, 原來(lái) x 的值有可能沒(méi)有改變, 也有可能改變. 為了防止出現(xiàn)這種不確定效應(yīng), Julia 限定如果參數(shù)是值傳遞, 其內(nèi)部域的值不可改變。

被聲明類型

以上的三種類型是緊密相關(guān)的。它們有相同的特性:

  • 明確地被聲明
  • 有名字
  • 有明確的父類
  • 可以有參數(shù)

正因有共有的特性,這些類型內(nèi)在地表達(dá)為同一種概念的實(shí)例,DataType,是以下類型之一:

    julia> typeof(Real)
    DataType

    julia> typeof(Int)
    DataType

DataType 既可以抽象也可以具體。如果是具體的,它會(huì)擁有既定的大小,存儲(chǔ)安排和(可選的)名域。所以一個(gè)位類型是一個(gè)大小非零的 DataType,但沒(méi)有名域。一個(gè)復(fù)合類型是一個(gè)可能擁有名域也可以為空集(大小為零)的 DataType 。

在這個(gè)系統(tǒng)里的每一個(gè)具體的值都是某個(gè) DataType 的實(shí)例,或者一個(gè)多元組。

多元組類型

多元組的類型是類型的多元組:

    julia> typeof((1,"foo",2.5))
    (Int64,ASCIIString,Float64)

類型多元組可以在任何需要類型的地方使用:

    julia> (1,"foo",2.5) :: (Int64,String,Any)
    (1,"foo",2.5)

    julia> (1,"foo",2.5) :: (Int64,String,Float32)
    ERROR: type: typeassert: expected (Int64,String,Float32), got (Int64,ASCIIString,Float64)

如果類型多元組中有非類型出現(xiàn),會(huì)報(bào)錯(cuò):

    julia> (1,"foo",2.5) :: (Int64,String,3)
    ERROR: type: typeassert: expected Type{T<:Top}, got (DataType,DataType,Int64)

注意,空多元組 () 的類型是其本身:

    julia> typeof(())
    ()

多元組類型是關(guān)于它的組成類型是協(xié)變的,一個(gè)多元組是另一個(gè)多元組的子類型意味著對(duì)應(yīng)的第一個(gè)多元組的各元素的類型是第二個(gè)多元組對(duì)應(yīng)元素類型的子類型。比如:

    julia> (Int,String) <: (Real,Any)
    true

    julia> (Int,String) <: (Real,Real)
    false

    julia> (Int,String) <: (Real,)
    false

直觀地看,這就像一個(gè)函數(shù)的各個(gè)參數(shù)的類型必須是函數(shù)簽名的子類型(當(dāng)簽名匹配的時(shí)候)。

類型共用體

類型共用體是特殊的抽象類型,使用 Union 函數(shù)來(lái)聲明:

    julia> IntOrString = Union(Int,String)
    Union(String,Int64)

    julia> 1 :: IntOrString
    1

    julia> "Hello!" :: IntOrString
    "Hello!"

    julia> 1.0 :: IntOrString
    ERROR: type: typeassert: expected Union(String,Int64), got Float64

不含任何類型的類型共用體,是“底”類型 None

    julia> Union()
    None

抽象類型 None 是所有其它類型的子類型,且沒(méi)有實(shí)例。零參的 Union 調(diào)用,將返回?zé)o實(shí)例的類型 None 。

參數(shù)化類型

Julia 的類型系統(tǒng)支持參數(shù)化:類型可以引入?yún)?shù),這樣類型聲明為每種可能的參數(shù)組合聲明一個(gè)新類型。

所有被聲明的類型(DataType 的變體)都可以使用同樣的語(yǔ)法來(lái)參數(shù)化。我們將按照如下順序來(lái)討論:參數(shù)化符合類型、參數(shù)化抽象類型、參數(shù)化位類型。

參數(shù)化復(fù)合類型

    abstract Pointy{T}
    type Point{T} <: Pointy{T}
      x::T
      y::T
    end

類型參數(shù)跟在類型名后,用花括號(hào)括起來(lái):

    type Point{T}
      x::T
      y::T
    end

這個(gè)聲明定義了新參數(shù)化類型 Point{T} ,它有兩個(gè) T 類型的“坐標(biāo)軸”。參數(shù)化類型可以是任何類型(也可以是整數(shù),此例中我們用的是類型)。具體類型 Point{Float64} 等價(jià)于將 Point 中的 T 替換為 Float64 后的類型。上例實(shí)際上聲明了許多種類型:Point{Float64}, Point{String}, Point{Int64} 等等,因此,現(xiàn)在每個(gè)都是可以使用的具體類型:

    julia> Point{Float64}
    Point{Float64} (constructor with 1 method)

    julia> Point{String}
    Point{String} (constructor with 1 method)

Point 本身也是個(gè)有效的類型對(duì)象:

    julia> Point
    Point{T} (constructor with 1 method)

Point 在這兒是一個(gè)抽象類型,它包含所有如 Point{Float64}, Point{String} 之類的具體實(shí)例:

    julia> Point{Float64} <: Point
    true

    julia> Point{String} <: Point
    true

其它類型則不是其子類型:

    julia> Float64 <: Point
    false

    julia> String <: Point
    false

Point 不同 T 值所聲明的具體類型之間,不能互相作為子類型:

    julia> Point{Float64} <: Point{Int64}
    false

    julia> Point{Float64} <: Point{Real}
    false

這一點(diǎn)非常重要:

雖然 Float64 <: Real Point{Float64} <: Point{Real} 不成立!

換句話說(shuō),Julia 的類型參數(shù)是 不相關(guān) 的。盡管 Point{Float64} 的實(shí)例按照概念來(lái)說(shuō),應(yīng)該是 Point{Real} 的實(shí)例,但兩者在內(nèi)存中的表示上有區(qū)別:

  • Point{Float64} 的實(shí)例可以簡(jiǎn)便、有效地表示 64 位數(shù)對(duì)兒
  • Point{Real} 的實(shí)例可以表示任意 Real 實(shí)例的數(shù)對(duì)兒。由于 Real 的實(shí)例可以為任意大小、任意結(jié)構(gòu),因此 Point{Real} 實(shí)際上表示指向 Real 對(duì)象的指針對(duì)兒

上述區(qū)別在數(shù)組中更明顯: Array{Float64} 可以在一塊連續(xù)內(nèi)存中存儲(chǔ) 64 位浮點(diǎn)數(shù),而 Array{Real} 則保存指向每個(gè) Real 對(duì)象的指針數(shù)組。而每個(gè) Real 對(duì)象的大小,可能比 64 位浮點(diǎn)數(shù)的大。

構(gòu)造函數(shù)中將介紹如何給復(fù)合類型自定義構(gòu)造方法,但如果沒(méi)有特殊構(gòu)造聲明時(shí),默認(rèn)有兩種構(gòu)造新復(fù)合對(duì)象的方法:一種是明確指明構(gòu)造方法的類型參數(shù);另一種是由對(duì)象構(gòu)造方法的參數(shù)來(lái)隱含類型參數(shù)。

指明構(gòu)造方法的類型參數(shù):

    julia> Point{Float64}(1.0,2.0)
    Point{Float64}(1.0,2.0)

    julia> typeof(ans)
    Point{Float64} (constructor with 1 method)

參數(shù)個(gè)數(shù)應(yīng)與構(gòu)造函數(shù)相匹配:

    julia> Point{Float64}(1.0)
    ERROR: no method Point{Float64}(Float64)

    julia> Point{Float64}(1.0,2.0,3.0)
    ERROR: no method Point{Float64}(Float64, Float64, Float64)

對(duì)于帶有類型參數(shù)的類型,因?yàn)橹剌d構(gòu)造函數(shù)是不可能的,所以只有一種默認(rèn)構(gòu)造函數(shù)被自動(dòng)生成——這個(gè)構(gòu)造函數(shù)接受任何參數(shù)并且把們轉(zhuǎn)換成對(duì)應(yīng)的字段類型并賦值

大多數(shù)情況下不需要提供 Point 對(duì)象的類型,它可由參數(shù)類型來(lái)提供信息。因此,可以不提供 T 的值:

    julia> Point(1.0,2.0)
    Point{Float64}(1.0,2.0)

    julia> typeof(ans)
    Point{Float64} (constructor with 1 method)

    julia> Point(1,2)
    Point{Int64}(1,2)

    julia> typeof(ans)
    Point{Int64} (constructor with 1 method)

上例中,Point 的兩個(gè)參數(shù)類型相同,因此 T 可以省略。但當(dāng)參數(shù)類型不同時(shí),會(huì)報(bào)錯(cuò):

    julia> Point(1,2.5)
    ERROR: `Point{T}` has no method matching Point{T}(::Int64, ::Float64)

這種情況其實(shí)也可以處理,詳見(jiàn)構(gòu)造函數(shù)

參數(shù)化抽象類型

類似地,參數(shù)化抽象類型聲明一個(gè)抽象類型的集合:

    abstract Pointy{T}

對(duì)每個(gè)類型或整數(shù)值 T,Pointy{T} 都是一個(gè)不同的抽象類型。Pointy 的每個(gè)實(shí)例都是它的子類型:

    julia> Pointy{Int64} <: Pointy
    true

    julia> Pointy{1} <: Pointy
    true

參數(shù)化抽象類型也是不相關(guān)的:


    julia> Pointy{Float64} <: Pointy{Real}
    false

    julia> Pointy{Real} <: Pointy{Float64}
    false

可以如下聲明 Point{T}Pointy{T} 的子類型:

    type Point{T} <: Pointy{T}
      x::T
      y::T
    end

對(duì)每個(gè) T,都有 Point{T}Pointy{T} 的子類型:

    julia> Point{Float64} <: Pointy{Float64}
    true

    julia> Point{Real} <: Pointy{Real}
    true

    julia> Point{String} <: Pointy{String}
    true

它們?nèi)匀皇遣幌嚓P(guān)的:

    julia> Point{Float64} <: Pointy{Real}
    false

參數(shù)化抽象類型 Pointy 有什么用呢?假設(shè)我們要構(gòu)造一個(gè)坐標(biāo)點(diǎn)的實(shí)現(xiàn),點(diǎn)都在對(duì)角線 x = y 上,因此我們只需要一個(gè)坐標(biāo)軸:


    type DiagPoint{T} <: Pointy{T}
      x::T
    end

Point{Float64}DiagPoint{Float64} 都是 Pointy{Float64} 抽象類型的實(shí)現(xiàn),這對(duì)其它可選類型 T 也一樣。 Pointy 可以作為它的子類型的公共接口。有關(guān)方法和重載,詳見(jiàn)下一節(jié) :ref:man-methods 。

有時(shí)需要對(duì) T 的范圍做限制:

    abstract Pointy{T<:Real}

此時(shí), T 只能是 Real 的子類型:

    julia> Pointy{Float64}
    Pointy{Float64}

    julia> Pointy{Real}
    Pointy{Real}

    julia> Pointy{String}
    ERROR: type: Pointy: in T, expected T<:Real, got Type{String}

    julia> Pointy{1}
    ERROR: type: Pointy: in T, expected T<:Real, got Int64

參數(shù)化復(fù)合類型的類型參數(shù),也可以同樣被限制:

    type Point{T<:Real} <: Pointy{T}
      x::T
      y::T
    end

下面是 Julia 的 Rational 的 immutable 類型是如何定義的,這個(gè)類型表示分?jǐn)?shù):

    immutable Rational{T<:Integer} <: Real
      num::T
      den::T
    end

單態(tài)類型

單態(tài)類型是一種特殊的抽象參數(shù)化類型。對(duì)每個(gè)類型 T ,抽象類型“單態(tài)” Type{T} 的實(shí)例為對(duì)象 T。來(lái)看些例子:

    julia> isa(Float64, Type{Float64})
    true

    julia> isa(Real, Type{Float64})
    false

    julia> isa(Real, Type{Real})
    true

    julia> isa(Float64, Type{Real})
    false

換句話說(shuō),僅當(dāng) AB 是同一個(gè)對(duì)象,且此對(duì)象是類型時(shí),isa(A,Type{B}) 才返回真。沒(méi)有參數(shù)時(shí),Type 僅是抽象類型,所有的類型都是它的實(shí)例,包括單態(tài)類型:

    julia> isa(Type{Float64},Type)
    true

    julia> isa(Float64,Type)
    true

    julia> isa(Real,Type)
    true

只有對(duì)象是類型時(shí),才是 Type 的實(shí)例:

    julia> isa(1,Type)
    false

    julia> isa("foo",Type)
    false

Julia 中只有類型對(duì)象才有單態(tài)類型。

參數(shù)化位類型

可以參數(shù)化地聲明位類型。例如,Julia 中指針被定義為位類型:

    # 32-bit system:
    bitstype 32 Ptr{T}

    # 64-bit system:
    bitstype 64 Ptr{T}

這兒的參數(shù)類型 T 不是用來(lái)做類型定義,而是個(gè)抽象標(biāo)簽,它定義了一組結(jié)構(gòu)相同的類型,這些類型僅能由類型參數(shù)來(lái)區(qū)分。盡管 Ptr{Float64}Ptr{Int64} 的表示是一樣的,它們是不同的類型。所有的特定指針類型,都是 Ptr 類型的子類型:

    julia> Ptr{Float64} <: Ptr
    true

    julia> Ptr{Int64} <: Ptr
    true

類型別名

Julia 提供 typealias 機(jī)制來(lái)實(shí)現(xiàn)類型別名。如,UintUint32Uint64 的類型別名,這取決于系統(tǒng)的指針大?。?/p>

    # 32-bit system:
    julia> Uint
    Uint32

    # 64-bit system:
    julia> Uint
    Uint64

它是通過(guò) base/boot.jl 中的代碼實(shí)現(xiàn)的:

    if is(Int,Int64)
        typealias Uint Uint64
    else
        typealias Uint Uint32
    end

對(duì)參數(shù)化類型,typealias 提供了簡(jiǎn)單的參數(shù)化類型名。Julia 的數(shù)組類型為 Array{T,n} ,其中 T 是元素類型, n 是數(shù)組維度的數(shù)值。為簡(jiǎn)單起見(jiàn),Array{Float64} 可以只指明元素類型而不需指明維度:

    julia> Array{Float64,1} <: Array{Float64} <: Array
    true

``Vector`` 和 ``Matrix`` 對(duì)象是如下定義的:

    typealias Vector{T} Array{T,1}
    typealias Matrix{T} Array{T,2}

類型運(yùn)算

Julia 中,類型本身也是對(duì)象,可以對(duì)其使用普通的函數(shù)。如 <: 運(yùn)算符,可以判斷左側(cè)是否是右側(cè)的子類型。

isa 函數(shù)檢測(cè)對(duì)象是否屬于某個(gè)指定的類型:

    julia> isa(1,Int)
    true

    julia> isa(1,FloatingPoint)
    false

typeof 函數(shù)返回參數(shù)的類型。類型也是對(duì)象,因此它也有類型:

    julia> typeof(Rational)
    DataType

    julia> typeof(Union(Real,Float64,Rational))
    DataType

    julia> typeof((Rational,None))
    (DataType,UnionType)

類型的類型是什么?它們的類型是 DataType

    julia> typeof(DataType)
    DataType

    julia> typeof(UnionType)
    DataType

讀者也許會(huì)注意到,DataType 類似于空多元組(詳見(jiàn)上文 )。因此,遞歸使用 ()DataType 所組成的多元組的類型,是該類型本身:

    julia> typeof(())
    ()

    julia> typeof(DataType)
    DataType

    julia> typeof(((),))
    ((),)

    julia> typeof((DataType,))
    (DataType,)

    julia> typeof(((),DataType))
    ((),DataType)

super 可以指明一些類型的父類型。只有聲明的類型(DataType)才有父類型:

    julia> super(Float64)
    FloatingPoint

    julia> super(Number)
    Any

    julia> super(String)
    Any

    julia> super(Any)
    Any

對(duì)其它類型對(duì)象(或非類型對(duì)象)使用 super ,會(huì)引發(fā) “no method” 錯(cuò)誤:

    julia> super(Union(Float64,Int64))
    ERROR: `super` has no method matching super(::Type{Union(Float64,Int64)})

    julia> super(None)
    ERROR: `super` has no method matching super(::Type{None})

    julia> super((Float64,Int64))
    ERROR: `super` has no method matching super(::Type{(Float64,Int64)})


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)