Julia 常見問題

2018-08-12 21:26 更新

常見問題

會(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ù)測(cè)出來。特別地,這表示輸出的類型不能因輸入的值的變化而變化。下面這段代碼 不是 類型穩(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ù)測(cè)函數(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。現(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ù)測(cè)到。您要問自己我是在遍歷存儲(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ù)檢測(cè)。

有些函數(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),測(cè)試版(beta),或者是夜間版(nightly version)?

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

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

如果您想利用語言的最新更新,您可能更喜歡使用 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 命令。
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)