鍍金池/ 教程/ Java/ 常見問題
類型轉(zhuǎn)換和類型提升
方法
嵌入式 Julia
交互
調(diào)用 C 和 Fortran 代碼
類型
代碼性能優(yōu)化
多維數(shù)組
元編程
函數(shù)
簡介
線性代數(shù)
與其它語言的區(qū)別
數(shù)學(xué)運(yùn)算和基本函數(shù)
構(gòu)造函數(shù)
控制流
常見問題
并行計(jì)算
擴(kuò)展包
開發(fā)擴(kuò)展包
開始
字符串
運(yùn)行外部程序
變量的作用域
模塊
網(wǎng)絡(luò)和流
代碼樣式
復(fù)數(shù)和分?jǐn)?shù)
可空類型
整數(shù)和浮點(diǎn)數(shù)
變量
日期和時(shí)間

常見問題

會(huì)話和 REPL

如何刪除內(nèi)存中的對(duì)象?

Julia 沒有 MATLAB 的 clear 函數(shù);在 Julia 會(huì)話(準(zhǔn)確來說,Main 模塊)中定義了一個(gè)名字的話,它就一直在啦。

如果你很關(guān)心內(nèi)存使用,你可以用占內(nèi)存的小的來替換大的。例如,如果 A 是個(gè)你不需要的大數(shù)組,可以先用 A = 0 來釋放內(nèi)存。下一次進(jìn)行垃圾回收的時(shí)候,內(nèi)存就會(huì)被釋放了;你也可以直接調(diào)用 gc() 來回收。

如何在會(huì)話中修改 type/immutable 的聲明?

有時(shí)候你定義了一種類型但是后來發(fā)現(xiàn)你需要添加一個(gè)新的域。當(dāng)你嘗試在 REPL 里這樣做時(shí)就會(huì)出錯(cuò)

    ERROR: invalid redefinition of constant MyType

Main 模塊里的類型不能被重新定義。

當(dāng)你在開發(fā)新代碼時(shí)這會(huì)變得極其不方便,有一個(gè)很好的辦法來處理。模塊是可以用重新定義的辦法來替換,所以把你的所有的代碼封裝在一個(gè)模塊里就能夠重新定義類型以及常數(shù)。你不能把類型名導(dǎo)入到 Main 里再去重新定義,但是你可以用模塊名來解決這個(gè)問題。換句話說,當(dāng)你開發(fā)的時(shí)候可以用這樣的工作流

    include("mynewcode.jl")              # this defines a module MyModule
    obj1 = MyModule.ObjConstructor(a, b)
    obj2 = MyModule.somefunction(obj1)
    # Got an error. Change something in "mynewcode.jl"
    include("mynewcode.jl")              # reload the module
    obj1 = MyModule.ObjConstructor(a, b) # old objects are no longer valid, must reconstruct
    obj2 = MyModule.somefunction(obj1)   # this time it worked!
    obj3 = MyModule.someotherfunction(obj2, c)
    ...

函數(shù)

我把參數(shù) x 傳遞給一個(gè)函數(shù), 并在函數(shù)內(nèi)修改它的值, 但是在函數(shù)外 x 的值并未發(fā)生變化, 為什么呢?

假設(shè)你像這樣調(diào)用函數(shù):

julia> x = 10
julia> function change_value!(y) # Create a new function
           y = 17
       end
julia> change_value!(x)
julia> x # x is unchanged!
10

在 Julia 里, 所有的函數(shù)(包括 change_value!()) 都不能修改局部變量的所屬的類。如果 x 被函數(shù)調(diào)用時(shí)被定義為一個(gè)不可變的對(duì)象(比如實(shí)數(shù)), 就不能修改; 同樣地,如果 x 被定義為一個(gè) Dict 對(duì)象,你不能把它改成 ASCIIString。但是需要主要的是: 假設(shè) x 是一個(gè)數(shù)組(或者任何可變類型)。 你不能讓 x 不再代表這個(gè)數(shù)組,但由于數(shù)組是可變的對(duì)象,你能修改數(shù)組的元素:

    julia> x = [1,2,3]
    3-element Array{Int64,1}:
    1
    2
    3

    julia> function change_array!(A) # Create a new function
               A[1] = 5
           end
    julia> change_array!(x)
    julia> x
    3-element Array{Int64,1}:
    5
    2
    3

這里我們定義了函數(shù) change_array!(), 把整數(shù) 5 分配給了數(shù)組的第一個(gè)元素。 當(dāng)我們把 x 傳讀給這個(gè)函數(shù)時(shí),注意到 x 依然是同一個(gè)數(shù)組,只是數(shù)組的元素發(fā)生了變化。

我能在函數(shù)中使用 using 或者 import 嗎?

不行,在函數(shù)中不能使用 usingimport。如果你要導(dǎo)入一個(gè)模塊但只是在某些函數(shù)里使用,你有兩種方案::

  1. 使用 import
        import Foo
        function bar(...)
            ... refer to Foo symbols via Foo.baz ...
        end
  1. 把函數(shù)封裝到模塊里:
        module Bar
        export bar
        using Foo
        function bar(...)
            ... refer to Foo.baz as simply baz ....
        end
        end
        using Bar

類型,類型聲明和構(gòu)造方法

什么是“類型穩(wěn)定”?

這意味著輸出的類型是可以由輸入類型預(yù)測出來。特別地,這表示輸出的類型不能因輸入的值的變化而變化。下面這段代碼 不是 類型穩(wěn)定的

    function unstable(flag::Bool)
        if flag
            return 1
        else
            return 1.0
        end
    end

這段代碼視參數(shù)的值的不同而返回一個(gè) Int 或是 Float64。 因?yàn)?Julia 無法在編譯時(shí)預(yù)測函數(shù)返回值類型,任何使用這個(gè)函數(shù)的計(jì)算都得考慮這兩種可能的返回類型,這樣很難生成快速的機(jī)器碼。

為什么看似合理的運(yùn)算 Julia 還是返回 DomainError?

有些運(yùn)算數(shù)學(xué)上講得通但是會(huì)產(chǎn)生錯(cuò)誤:

    julia> sqrt(-2.0)
    ERROR: DomainError
     in sqrt at math.jl:128

    julia> 2^-5
    ERROR: DomainError
     in power_by_squaring at intfuncs.jl:70
     in ^ at intfuncs.jl:84

這時(shí)由類型穩(wěn)定造成的。對(duì)于 sqrt, 大多數(shù)用戶會(huì)用 sqrt(2.0) 得到一個(gè)實(shí)數(shù)而不是得到一個(gè)復(fù)數(shù) 1.4142135623730951 + 0.0im。 也可以把 sqrt 寫成當(dāng)參數(shù)為負(fù)的時(shí)候返回復(fù)數(shù),但是這將不再是 類型穩(wěn)定而且 sqrt 會(huì)變的很慢。

在這些情況下,你可以選擇 輸入類型 來得到想要的 輸出類型 :

    julia> sqrt(-2.0+0im)
    0.0 + 1.4142135623730951im

    julia> 2.0^-5
    0.03125

Julia 為什么使用本機(jī)整數(shù)運(yùn)算?

Julia 會(huì)應(yīng)用機(jī)器運(yùn)算的整數(shù)計(jì)算。這意味著 int 值的范圍是有界的,是在兩界之間取值的,所以添加,減去,乘以和除以一個(gè)整數(shù)都可能導(dǎo)致上溢或下溢,這可能會(huì)導(dǎo)致一些不好的后果,這種情況在一開始會(huì)讓人感到很不安。

    julia> typemax(Int)
    9223372036854775807

    julia> ans+1
    -9223372036854775808

    julia> -ans
    -9223372036854775808

    julia> 2*ans
    0

顯然,這遠(yuǎn)遠(yuǎn)不能用數(shù)學(xué)的方法來表現(xiàn),您可能會(huì)認(rèn)為 Julia 與一些高級(jí)編程語言會(huì)公開給用戶這一情況相比來說不是那么理想。然而這對(duì)于效率和透明度都非常珍貴的數(shù)值工作來說,相比之下,替代品更是糟糕。

這里有一個(gè)選擇是來檢查每個(gè)整數(shù)操作的溢出情況,并且由于溢出情況而提高結(jié)果值到大一些的整數(shù)類型,例如 Int128BigInt。不幸的是,這就引進(jìn)了在每個(gè)整數(shù)操作上都會(huì)有的主要負(fù)擔(dān)(想想增加一個(gè)循環(huán)計(jì)數(shù)器) - 這需要發(fā)射代碼在算術(shù)指令后執(zhí)行程序時(shí)的溢出檢查,并且需要一些分支來解決潛在溢出問題。更糟糕的是,這會(huì)導(dǎo)致每一個(gè)計(jì)算,在涉及整數(shù)時(shí)都是不穩(wěn)定的。正如我們上面提到的,類型的穩(wěn)定性是有效的代碼生成的關(guān)鍵。如果您不能指望整數(shù)運(yùn)算的結(jié)果是整數(shù),那么按 C 和 Fortran 編譯器方式做的簡單代碼,想要生成速度快是不可能的。

這個(gè)方法還有一個(gè)可以避免不穩(wěn)定類型外觀的變化,就是把 IntBigInt 合并成一個(gè)單一的混合整數(shù)類型,當(dāng)結(jié)果不再適合機(jī)器整數(shù)的大小時(shí),可以由內(nèi)部改變來表示。然而這只是表面上解決了 Julia 語言的不穩(wěn)定性水平問題,它也僅僅只是通過強(qiáng)硬地把所有相同的難題匯于 C 語言,使混合整數(shù)類型可以成功實(shí)現(xiàn)的方式,解決了幾個(gè)小問題而已。這種方法基本上可以進(jìn)行工作,甚至可以在許多情況下可以作出相當(dāng)快的反應(yīng),但是還是有幾個(gè)缺點(diǎn)的。其中一個(gè)問題是,在內(nèi)存中整數(shù)和整數(shù)數(shù)組的表示方法,不再和 C,F(xiàn)ortran 等其它具有本地機(jī)器整數(shù)的語言的本地表示方法一一對(duì)應(yīng)了。因此,對(duì)這些語言進(jìn)行互操作,我們無論如何最終都需要引入本地的整數(shù)類型。任何無界表示的整數(shù)都沒有一個(gè)固定的位,因此它們不能內(nèi)聯(lián)地被存儲(chǔ)在有固定大小的槽的數(shù)組里,較大的整數(shù)的值會(huì)一直需要單獨(dú)的堆分配來進(jìn)行存儲(chǔ)。當(dāng)然,不管一個(gè)混合整數(shù)的實(shí)現(xiàn)有多精妙,總會(huì)有性能陷阱的情況或是性能下降的情況。復(fù)雜的表示的話,缺乏與 C 和 Fortran 語言的互操作性,不能代表沒有額外堆存儲(chǔ)的整數(shù)數(shù)組,并且不可預(yù)知的性能特點(diǎn)使即使最精妙的混合整數(shù)來實(shí)現(xiàn)高性能計(jì)算的工作不管怎樣都不是個(gè)好辦法。

還有一個(gè)在使用混合整數(shù)或是使其提高到 BigInts 的選擇是用飽和的整數(shù)運(yùn)算實(shí)現(xiàn)的,這個(gè)運(yùn)算使即使把一個(gè)數(shù)添加到最大的整數(shù)值,值也不會(huì)變,同樣的,從最小的整數(shù)值減去數(shù)值,值也不變。這恰恰就是 Matlab? 可以實(shí)現(xiàn)的。

    >> int64(9223372036854775807)

    ans =

      9223372036854775807

    >> int64(9223372036854775807) + 1

    ans =

      9223372036854775807

    >> int64(-9223372036854775808)

    ans =

     -9223372036854775808

    >> int64(-9223372036854775808) - 1

    ans =

     -9223372036854775808

乍一看,這似乎很合理,因?yàn)?922337203685477580 是比 -922337203685477580 更要接近 922337203685477580 的,并且整數(shù)還是表現(xiàn)在一種用 C 語言和 Fortran 語言兼容的固定大小實(shí)現(xiàn)的本地的方式。然而,飽和的整數(shù)運(yùn)算,是非常有問題的。首先的和最明顯的問題是,它不是機(jī)器的整數(shù)算術(shù)操作方式,所以每臺(tái)機(jī)器進(jìn)行整數(shù)運(yùn)算來檢查下溢或上溢,并且用 typemin(int)或 typemax(int) 適當(dāng)?shù)厝〈Y(jié)果之后,才可以實(shí)現(xiàn)發(fā)出飽和操作需要發(fā)出的指令。這就單獨(dú)將每一個(gè)整數(shù)運(yùn)算從一個(gè)單一的、快速的指令擴(kuò)展到 6 個(gè)指令,還可能包括分支。但它會(huì)變得更糟–飽和的整數(shù)算術(shù)并不是聯(lián)想的。來考慮這個(gè) MATLAB 計(jì)算:

    >> n = int64(2)^62
    4611686018427387904

    >> n + (n - 1)
    9223372036854775807

    >> (n + n) - 1
    9223372036854775806

這使得它很難寫很多基本的整數(shù)算法,因?yàn)楹芏喑R姷募夹g(shù)依賴于這樣一個(gè)事實(shí),即機(jī)器加成與溢出是聯(lián)想的??紤]在 Julia 中利用 (lo + hi) >>> 1 表達(dá)式來找到整數(shù)值 lo 和 hi 的中間點(diǎn):

    julia> n = 2^62
    4611686018427387904

    julia> (n + 2n) >>> 1
    6917529027641081856

看見了嗎?沒有問題。這是 2^62 和 2^63 之間正確的中點(diǎn),盡管 n+2n 實(shí)際應(yīng)是 - 461168601842738790?,F(xiàn)在嘗試在 MATLAB 中:

    >> (n + 2*n)/2

    ans =

      4611686018427387904

這就出錯(cuò)了。添加一個(gè) a >>> 運(yùn)算元到 Matlab 上并不會(huì)有幫助。因?yàn)樘砑?n 和 2n 已經(jīng)破壞了必要的計(jì)算正確的中點(diǎn)的信息時(shí),飽和就發(fā)生了。

這不僅是程序員缺乏結(jié)合性而不幸不能依賴這樣的技術(shù),而且還打敗幾乎任何編譯器可能想做的優(yōu)化整數(shù)運(yùn)算。例如,由于 Julia 的整數(shù)使用正常的機(jī)器整數(shù)運(yùn)算,LLVM 是自由的積極簡單的優(yōu)化小函數(shù)如 f(k)= 5k-1。這個(gè)函數(shù)的機(jī)器碼就是這樣的:

    julia> code_native(f,(Int,))
        .section    __TEXT,__text,regular,pure_instructions
    Filename: none
    Source line: 1
        push    RBP
        mov RBP, RSP
    Source line: 1
        lea RAX, QWORD PTR [RDI + 4*RDI - 1]
        pop RBP
        ret

函數(shù)的實(shí)際體是一個(gè)單一的 lea 指令,計(jì)算整數(shù)時(shí)立刻進(jìn)行乘,加運(yùn)算。當(dāng) f 被嵌入另一個(gè)函數(shù)時(shí),更加有利處:

    julia> function g(k,n)
             for i = 1:n
               k = f(k)
             end
             return k
           end
    g (generic function with 2 methods)

    julia> code_native(g,(Int,Int))
        .section    __TEXT,__text,regular,pure_instructions
    Filename: none
    Source line: 3
        push    RBP
        mov RBP, RSP
        test    RSI, RSI
        jle 22
        mov EAX, 1
    Source line: 3
        lea RDI, QWORD PTR [RDI + 4*RDI - 1]
        inc RAX
        cmp RAX, RSI
    Source line: 2
        jle -17
    Source line: 5
        mov RAX, RDI
        pop RBP
        ret

由于 f 調(diào)用被內(nèi)聯(lián),循環(huán)體的結(jié)束時(shí)只是一個(gè)單一的 lea 指令。接下來,如果我們使循環(huán)迭代次數(shù)固定,我們可以來考慮發(fā)生了什么:

    julia> function g(k)
             for i = 1:10
               k = f(k)
             end
             return k
           end
    g (generic function with 2 methods)

    julia> code_native(g,(Int,))
        .section    __TEXT,__text,regular,pure_instructions
    Filename: none
    Source line: 3
        push    RBP
        mov RBP, RSP
    Source line: 3
        imul    RAX, RDI, 9765625
        add RAX, -2441406
    Source line: 5
        pop RBP
        ret

因?yàn)榫幾g器知道整數(shù)的加法和乘法之間的聯(lián)系并且乘法分配時(shí)優(yōu)先級(jí)會(huì)高于除法 – 這兩者都是真正的飽和運(yùn)算 – 它們可以優(yōu)化整個(gè)回路使之只留下來的只是乘法和加法。飽和算法完全地打敗了這種最優(yōu)化,這是因?yàn)榻Y(jié)合性和分配性在每次在循環(huán)迭代都可能會(huì)失敗,而所導(dǎo)致的不同后果取決于在哪次迭代會(huì)失敗。

飽和整數(shù)算法只是一個(gè)真的很差的語言語義學(xué)選擇的例子,它可以阻止所有有效的性能優(yōu)化。在 C 語言編程中有很多事情是很難的,但整數(shù)溢出并不是其中之一,特別是在 64 位系統(tǒng)中。比如如果我用的整數(shù)可能會(huì)變得比 2^63-1 還要大,我可以很容易地預(yù)測到。您要問自己我是在遍歷存儲(chǔ)在計(jì)算機(jī)中的實(shí)際的東西么?之后我就可以確認(rèn)數(shù)是不會(huì)變得那么大的。這點(diǎn)是可以保證的,因?yàn)槲覜]那么大的存儲(chǔ)空間。我是真的在數(shù)實(shí)際真實(shí)存在的東西么?除非它們是宇宙中的沙子或原子粒,否則 2^63-1 已經(jīng)足夠大了。我是在計(jì)算階乘么?之后就可以確認(rèn),它們可能變得特別大-我就應(yīng)該用 BigInt 了??炊嗣??區(qū)分起來是很簡單的。

類型的“抽象的”或者不明確的域如何與編譯器進(jìn)行交互?

類型可以在不指定字段的類型的情況下聲明:

    julia> type MyAmbiguousType
               a
           end

這允許 a 是任何類型。這通常是非常有用的,但它有一個(gè)缺點(diǎn):對(duì)于 MyAmbiguousType 類型的對(duì)象,編譯器將無法生成高效的代碼。原因是編譯器使用對(duì)象的類型而不是值來決定如何構(gòu)建代碼。不幸的是,MyAmbiguousType 類型只能推斷出很少的信息:

    julia> b = MyAmbiguousType("Hello")
    MyAmbiguousType("Hello")

    julia> c = MyAmbiguousType(17)
    MyAmbiguousType(17)

    julia> typeof(b)
    MyAmbiguousType (constructor with 1 method)

    julia> typeof(c)
    MyAmbiguousType (constructor with 1 method)

bc 有著相同的類型,但是它們?cè)趦?nèi)存中數(shù)據(jù)的基礎(chǔ)表示是非常不同的。即使您只在 a 的域中儲(chǔ)存數(shù)值,事實(shí)上 Uint8Float64 的內(nèi)存表示不同也意味著 CPU 需要用兩種不同的指令來處理它們。由于類型中的所需信息是不可用,于是這樣的決定不得不在運(yùn)行時(shí)作出。這減緩了性能。

您可以用聲明 a 的類型的方法做得更好。在這里,我們注意到這樣一種情況,就是 a 可能是幾個(gè)類型中的任意一種,在這種情況下自然的解決辦法是使用參數(shù)。例如:

    julia> type MyType{T<:FloatingPoint}
             a::T
           end

這相對(duì)以下代碼是一個(gè)更好的選擇

    julia> type MyStillAmbiguousType
             a::FloatingPoint
           end

因?yàn)榈谝粋€(gè)版本指定了包裝對(duì)象的類型。例如:

    julia> m = MyType(3.2)
    MyType{Float64}(3.2)

    julia> t = MyStillAmbiguousType(3.2)
    MyStillAmbiguousType(3.2)

    julia> typeof(m)
    MyType{Float64} (constructor with 1 method)

    julia> typeof(t)
    MyStillAmbiguousType (constructor with 2 methods)

a 的域的類型可以輕而易舉地由 m 的類型確定,但不是從 t 的類型確定。事實(shí)上,在 t 中是可以改變 a 的域的類型的:

    julia> typeof(t.a)
    Float64

    julia> t.a = 4.5f0
    4.5f0

    julia> typeof(t.a)
    Float32

相反,一旦 m 被構(gòu)造,m.a 的類型就不能改變了:

    julia> m.a = 4.5f0
    4.5

    julia> typeof(m.a)
    Float64

a 的類型可以從 m 的類型知道的事實(shí)和 m.a 的類型不能在函數(shù)中修改的事實(shí)允許編譯器為像 m 那樣的類而不是像 t 那樣的類生成高度優(yōu)化的代碼。

當(dāng)然,只有當(dāng)我們用具體類型來構(gòu)造 m 時(shí),這一切才是真實(shí)的。我們可以通過明確地用抽象類構(gòu)造它的方法來打破之一點(diǎn):

    julia> m = MyType{FloatingPoint}(3.2)
    MyType{FloatingPoint}(3.2)

    julia> typeof(m.a)
    Float64

    julia> m.a = 4.5f0
    4.5f0

    julia> typeof(m.a)
    Float32

對(duì)于一切實(shí)際目的,這些對(duì)象對(duì) MyStillAmbiguousType 的行為相同。

對(duì)比一個(gè)簡單程序所產(chǎn)生的全部代碼是很有意義的:

    func(m::MyType) = m.a+1

使用:

    code_llvm(func,(MyType{Float64},))
    code_llvm(func,(MyType{FloatingPoint},))
    code_llvm(func,(MyType,))

由于長度的原因,結(jié)果并沒有在這里顯示,但您不妨自己嘗試一下。因?yàn)樵诘谝环N情況下,該類型是完全指定的,編譯器不需要在運(yùn)行時(shí)生成任何代碼來解決類型的問題。這就會(huì)有更短的代碼更快的編碼速度。

如何聲明“抽象容器類型”的域

與應(yīng)用在上一章節(jié)中的最好的相同例子在容器類型中也適用:

    julia> type MySimpleContainer{A<:AbstractVector}
             a::A
           end

    julia> type MyAmbiguousContainer{T}
             a::AbstractVector{T}
           end

例如:

    julia> c = MySimpleContainer(1:3);

    julia> typeof(c)
    MySimpleContainer{UnitRange{Int64}} (constructor with 1 method)

    julia> c = MySimpleContainer([1:3]);

    julia> typeof(c)
    MySimpleContainer{Array{Int64,1}} (constructor with 1 method)

    julia> b = MyAmbiguousContainer(1:3);

    julia> typeof(b)
    MyAmbiguousContainer{Int64} (constructor with 1 method)

    julia> b = MyAmbiguousContainer([1:3]);

    julia> typeof(b)
    MyAmbiguousContainer{Int64} (constructor with 1 method)

對(duì)于 MySimpleContainer,對(duì)象是由其類型和參數(shù)完全指定的,所以編譯器可以生成優(yōu)化的功能。在大多數(shù)情況下,這可能就足夠了。

雖然現(xiàn)在編譯器可以完美地完成它的工作,但某些時(shí)候您可能希望您的代碼能夠根據(jù) a元素類型做出不同的東西。通常,達(dá)到這一點(diǎn)最好的方法是把您的具體操作(這里是 foo)包在一個(gè)單獨(dú)的函數(shù)里:

    function sumfoo(c::MySimpleContainer)
        s = 0
    for x in c.a
        s += foo(x)
    end
    s
    end

    foo(x::Integer) = x
    foo(x::FloatingPoint) = round(x)

這在允許編譯器在所有情況下都生成優(yōu)化的代碼,同時(shí)保持做起來很簡單。

然而,有時(shí)候您需要根據(jù) a 的不同的元素類型來聲明外部函數(shù)的不同版本。您可以像這樣來做:

    function myfun{T<:FloatingPoint}(c::MySimpleContainer{Vector{T}})
        ...
    end
    function myfun{T<:Integer}(c::MySimpleContainer{Vector{T}})
        ...
    end

這對(duì)于 Vector{T} 來講不錯(cuò),但是我們也要給 UnitRange{T} 或其他抽象類寫明確的版本。為了防止這樣單調(diào)乏味的情況,您可以在 MyContainer 的聲明中來使用兩個(gè)變量:

    type MyContainer{T, A<:AbstractVector}
        a::A
    end
    MyContainer(v::AbstractVector) = MyContainer{eltype(v), typeof(v)}(v)

    julia> b = MyContainer(1.3:5);

    julia> typeof(b)
    MyContainer{Float64,UnitRange{Float64}}

請(qǐng)注意一個(gè)有點(diǎn)令人驚訝的事實(shí),T 沒有在 a 的域中聲明,一會(huì)之后我們將會(huì)回到這一點(diǎn)。用這種方法,一個(gè)人可以編寫像這樣的函數(shù):

    function myfunc{T<:Integer, A<:AbstractArray}(c::MyContainer{T,A})
        return c.a[1]+1
    end
    # Note: because we can only define MyContainer for
    # A<:AbstractArray, and any unspecified parameters are arbitrary,
    # the previous could have been written more succinctly as
    #     function myfunc{T<:Integer}(c::MyContainer{T})

    function myfunc{T<:FloatingPoint}(c::MyContainer{T})
        return c.a[1]+2
    end

    function myfunc{T<:Integer}(c::MyContainer{T,Vector{T}})
        return c.a[1]+3
    end

    julia> myfunc(MyContainer(1:3))
    2

    julia> myfunc(MyContainer(1.0:3))
    3.0

    julia> myfunc(MyContainer([1:3]))
    4

正如您所看到的,用這種方法可以既專注于元素類型 T 也專注于數(shù)組類型 A

然而還剩下一個(gè)問題:我們沒有強(qiáng)制使 A 包括元素類型 T,所以完全有可能構(gòu)造這樣一個(gè)對(duì)象:

  julia> b = MyContainer{Int64, UnitRange{Float64}}(1.3:5);

  julia> typeof(b)
  MyContainer{Int64,UnitRange{Float64}}

為了防止這一點(diǎn),我們可以添加一個(gè)內(nèi)部構(gòu)造函數(shù):

    type MyBetterContainer{T<:Real, A<:AbstractVector}
        a::A

        MyBetterContainer(v::AbstractVector{T}) = new(v)
    end
    MyBetterContainer(v::AbstractVector) = MyBetterContainer{eltype(v),typeof(v)}(v)

    julia> b = MyBetterContainer(1.3:5);

    julia> typeof(b)
    MyBetterContainer{Float64,UnitRange{Float64}}

    julia> b = MyBetterContainer{Int64, UnitRange{Float64}}(1.3:5);
    ERROR: no method MyBetterContainer(UnitRange{Float64},)

內(nèi)部構(gòu)造函數(shù)要求 A 的元素類型為 T

無和缺值

Julia 中的“空(null)”和“無(nothingness)”如何工作?

不像許多其他語言(例如,C 和 Java)中的那樣,Julia 中沒有“空(null)”值。當(dāng)引用(變量,對(duì)象的域,或者數(shù)組元素)是未初始化的,訪問它就會(huì)立即拋出一個(gè)錯(cuò)誤。這種情況可以通過 isdefined 函數(shù)檢測。

有些函數(shù)只用于其副作用,不需要返回值。在這種情況下,慣例返回 nothing,它只是一個(gè) Nothing 類型的對(duì)象。這是一個(gè)沒有域的普通類型;它除了這個(gè)慣例之外,沒有什么特殊的,并且 REPL 不會(huì)為它打印任何東西。一些不能有值的語言結(jié)構(gòu)也統(tǒng)一為 nothing,例如 if false; end。

注意 Nothing(大寫)是 nothing 的類型,并且只應(yīng)該用在一個(gè)類型被需求環(huán)境中(例如一個(gè)聲明)。

您可能偶爾看到 None,這是完全不同的。它是空(empty,或是“底” bottom)類型,一類沒有值也沒有子類型(subtypes,除了它本身)的類型。您一般不需要使用這種類型。

空元組(())是另一種類型的無。但是它不應(yīng)該真的被認(rèn)為是什么都沒有而是一個(gè)零值的元組。

Julia 發(fā)行版

我想要使用一個(gè) Julia 的發(fā)行版本(release),測試版(beta),或者是夜間版(nightly version)?

如果您想要一個(gè)穩(wěn)定的代碼基礎(chǔ),您可能更傾向于 Julia 的發(fā)行版本。一般情況下每 6 個(gè)月發(fā)布一次,給您一個(gè)穩(wěn)定的寫代碼平臺(tái)。

如果您不介意稍稍落后于最新的錯(cuò)誤修正和更改的話,但是發(fā)現(xiàn)更具有吸引力的更改的更快一點(diǎn)的速度,您可能更喜歡 Julia 測試版本。此外,這些二進(jìn)制文件在發(fā)布之前進(jìn)行測試,以確保它們是具有完全功能的。

如果您想利用語言的最新更新,您可能更喜歡使用 Julia 的夜間版本,并且不介意這個(gè)版本偶爾不工作。

最后,您也可以考慮從源頭上為自己建造 Julia。此選項(xiàng)主要是對(duì)那些對(duì)命令行感到舒適或?qū)W(xué)習(xí)感興趣的個(gè)人。如果這描述了您,您可能也會(huì)感興趣在閱讀我們指導(dǎo)方針

這些下載類型的鏈接可以在下載頁面 http://julialang.org/downloads/ 找到。請(qǐng)注意,并非所有版本的 Julia 都可用于所有平臺(tái)。

何時(shí)移除舍棄的函數(shù)?

過時(shí)的功能在隨后的發(fā)行版本之后去除。例如,在 0.1 發(fā)行版本中被標(biāo)記為過時(shí)的功能將不會(huì)在 0.2 發(fā)行版本中使用。

開發(fā) Julia

我要如何調(diào)試 Julia 的 C 代碼?(從一個(gè)像是 gdb 的調(diào)試器內(nèi)部運(yùn)行 Julia REPL)

首先您應(yīng)該用 make debug 構(gòu)建 Julia 調(diào)試版本。下面,以(gdb)開頭的行意味著您需要在 gdb prompt 下輸入。

從 shell 開始

主要的挑戰(zhàn)是 Julia 和 gdb 都需要有它們自己的終端,來允許您和它們交互。一個(gè)方法是使用 gdb 的 attach 功能來調(diào)試一個(gè)已經(jīng)運(yùn)行的 Julia session。然而,在許多系統(tǒng)中,您需要使用根訪問(root access)來使這個(gè)工作。下面是一個(gè)可以只使用用戶級(jí)別權(quán)限來實(shí)現(xiàn)的方法。

第一次做這種事時(shí),您需要定義一個(gè)腳本,在這里被稱為 oterm,包含以下幾行:

    ps
    sleep 600000

讓它用 chmod +x oterm 執(zhí)行。

現(xiàn)在:

  • 從一個(gè) shell(被稱為 shell 1)開始,類型 xterm -e oterm &。您會(huì)看到一個(gè)新的窗口彈出,這將被稱為終端 2。
  • 從 shell 1 之內(nèi),gdb julia-debug。您將會(huì)在 julia/usr/bin 里找到這個(gè)可執(zhí)行文件。
  • 從 shell 1 之內(nèi),(gdb) tty /dev/pts/# 里面的 # 是在 terminal 2 中 pts/ 之后顯示的數(shù)字。
  • 從 shell 1 之內(nèi),(gdb) run
  • 從 terminal 2 之內(nèi),在 Julia 中發(fā)布任何準(zhǔn)備好的您需要的命令來進(jìn)入您想調(diào)試的步驟
  • 從 shell 1 之內(nèi),按 Ctrl-C
  • 從 shell 1 之內(nèi),插入您的斷點(diǎn),例如 (gdb) b codegen.cpp:2244
  • 從 shell 1 之內(nèi),(gdb) c 來繼續(xù) Julia 的執(zhí)行
  • 從 terminal 2 之內(nèi),發(fā)布您想要調(diào)試的命令,shell 1 將會(huì)停在您的斷點(diǎn)處。

在 emacs 之內(nèi)

  • M-x gdb,然后進(jìn)入 julia-debug(這可以最簡單的從 julia/usr/bin 找到,或者您可以指定完整路徑)
  • (gdb) run
  • 現(xiàn)在您將會(huì)看到 Julia prompt。在 Julia 中運(yùn)行任何您需要的命令來達(dá)到您想要調(diào)試的步驟。
  • 在 emacs 的 “Signals” 菜單下選擇 BREAK——這將會(huì)使您返回到 (gdb) prompt
  • 設(shè)置一個(gè)斷點(diǎn),例如,(gdb) b codegen.cpp:2244
  • 通過 (gdb) c 返回到 Julia prompt
  • 執(zhí)行您想要運(yùn)行的 Julia 命令。