鍍金池/ 教程/ 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í)間

代碼樣式

以下各節(jié)從幾方面介紹了符合語言習(xí)慣的 Julia 編碼風(fēng)格。這些規(guī)則都不是絕對的;它們僅僅是幫您熟悉這門語言,或是幫您可以在許多可替代性設(shè)計(jì)中能夠做出選擇的一些建議而已。

寫成函數(shù),別寫成腳本

編寫代碼作為在一系列步驟中最高級的辦法,是可以快速開始解決問題的,但您應(yīng)該試著盡快把一個(gè)程序分成許多函數(shù)。函數(shù)具有更好的可重用性和可測試性,并可以更好闡明它們正在做什么,它們的輸入和輸出是什么。此外,由于 Julia 的編譯器工作原理,在函數(shù)中的代碼往往比最高級別的代碼運(yùn)行得更快。

同樣值得強(qiáng)調(diào)的是,函數(shù)應(yīng)該以參數(shù)來代替,而不是直接在全局變量(除了像 pi 那樣的常量)上操作。

避免類型過于嚴(yán)格

代碼應(yīng)盡可能通用。相較于這樣的代碼書寫:

    convert(Complex{Float64}, x)

使用有效的泛型函數(shù)是更好的:

    complex(float(x))

第二種寫法把 x 轉(zhuǎn)換成一個(gè)適當(dāng)?shù)念愋?,而不是一直用一個(gè)相同的類型。

這種類型特點(diǎn)是特別地與函數(shù)自變量相關(guān)。例如,不聲明一個(gè)參數(shù)是 Int 類型或 Int32 類型,如果在這種情況下還可以保持是任何整數(shù),那就應(yīng)該是用 Integer 抽象表達(dá)出來的。事實(shí)上,在許多情況下您都可以把自變量類型給忽視掉,除非一些需要消除歧義的時(shí)候,由于如果一個(gè)類型不支持任何必要操作就會(huì)被忽略,那么一個(gè) MethodError 不管怎樣也都會(huì)被忽略掉。(這被大家認(rèn)為是 duck typing。)

例如,考慮以下 addone 函數(shù)中的定義,這個(gè)功能可以返回 1 加上它的自變量。

    addone(x::Int) = x + 1             # works only for Int
    addone(x::Integer) = x + one(x)    # any integer type
    addone(x::Number) = x + one(x)     # any numeric type
    addone(x) = x + one(x)             # any type supporting + and one

最后一個(gè) addone 的定義解決了所有類型的有關(guān)自變量的 one 函數(shù)(像 x 類型一樣返回 1 值,可以避免不想要的類型提供)和 + 函數(shù)的問題。關(guān)鍵是要意識到,僅僅是定義通用的 addone(x) = x + one(x) 寫法也是沒有性能缺失的,因?yàn)?Julia 會(huì)根據(jù)需要自主編譯到專業(yè)的版本。舉個(gè)例子,您第一次調(diào)用 addone(12) 的時(shí)候, Julia 會(huì)自動(dòng)為 x::Int 自變量編譯一個(gè) addone 函數(shù),通過調(diào)用一個(gè)內(nèi)聯(lián)值 1 代替 one。因此,上表前三個(gè)定義全都是重復(fù)的。

在調(diào)用程序中解決額外的自變量多樣性問題

取代這種寫法:

    function foo(x, y)
        x = int(x); y = int(y)
        ...
    end
    foo(x, y)

利用以下的寫法更好:

    function foo(x::Int, y::Int)
        ...
    end
    foo(int(x), int(y))

第二種寫法更好的方式,因?yàn)?foo 并沒有真正接受所有類型的數(shù)據(jù);它真正需要的是 Int S。

這里的一個(gè)問題是,如果一個(gè)函數(shù)本質(zhì)上需要整數(shù),可能更好的方式是強(qiáng)制調(diào)用程序來決定怎樣轉(zhuǎn)換非整數(shù)(例如最低值或最高值)。另一個(gè)問題是,聲明更具體的類型會(huì)為未來的方法定義提供更多的“空間”。

如果函數(shù)修改了它的參數(shù),在函數(shù)名后加 !

取代這種寫法:

    function double{T<:Number}(a::AbstractArray{T})
        for i = 1:endof(a); a[i] *= 2; end
    a
    end

利用以下寫法更好:

    function double!{T<:Number}(a::AbstractArray{T})
        for i = 1:endof(a); a[i] *= 2; end
    a
    end

Julia 標(biāo)準(zhǔn)庫在整個(gè)過程中使用以上約定,并且 Julia 標(biāo)準(zhǔn)庫還包含一些函數(shù)復(fù)制和修飾形式的例子(例如 sortsort!),或是其它只是在修飾(例如 push!, pop!,splice!)的例子。這對一些也要為了方便而返回修改后數(shù)組的函數(shù)來說是很典型的。

避免奇葩的類型集合

Union(Function,String) 這樣的類型,說明你的設(shè)計(jì)有問題。

盡量避免空域

當(dāng)使用 x::Union(Nothing,T) 時(shí),想想把 x 轉(zhuǎn)換成 nothing 這個(gè)選項(xiàng)是否是必要的。以下是一些可供選擇的替代選項(xiàng)

  • 找到一個(gè)安全的默認(rèn)值來和 x 一起初始化
  • 介紹另一種缺少 x 的類型
  • 如果有許多類似 x 的域,就把它們存儲(chǔ)在字典中
  • 確定當(dāng) xnoting 時(shí)是否有一個(gè)簡單的規(guī)則。例如,域通常是以 nothing 開始的,但是是在一些定義良好的點(diǎn)被初始化。在這種情況下,要首先考慮它可能沒被定義。

避免復(fù)雜的容器類型

通常情況下,像下面這樣創(chuàng)建數(shù)組是沒什么幫助的:

    a = Array(Union(Int,String,Tuple,Array), n)

在這種情況下 cell(n) 這樣寫更好一些。 這也有助于對編譯器進(jìn)行注釋這一特定用途,而不是試圖將許多選擇打包成一種類型。

使用和 Julia base/ 相同的命名傳統(tǒng)

  • 模塊和類型名稱以大寫開頭, 并且使用駝峰形式: module SparseMatrix, immutable UnitRange.
  • 函數(shù)名稱使用小寫 (maximum, convert). 在容易讀懂的情況下把幾 個(gè)單詞連在一起寫 (isequal, haskey). 在必要的情況下, 使用下劃 線作為單詞的分隔符. 下劃線也可以用來表示多個(gè)概念的組合 (remotecall_fetch 相比 remotecall(fetch(...)) 是一種更有效的 實(shí)現(xiàn)), 或者是為了區(qū)分 (sum_kbn). 簡潔是提倡的, 但是要避免縮寫 (indexin 而不是 indxin) 因?yàn)楹茈y記住某些單詞是否縮寫或者怎么 縮寫的.

如果一個(gè)函數(shù)需要多個(gè)單詞來描述, 想一下這個(gè)函數(shù)是否包含了多個(gè)概念, 這樣 的情況下最好分拆成多個(gè)部分.

不要濫用 try-catch

避免錯(cuò)誤要比依賴找錯(cuò)好多了。

不要把條件表達(dá)式用圓括號括起來

Julia 在 if 和 while 語句中不需要括號。所以要這樣寫:

    if a == b

來取代:

    if (a == b)

不要濫用 ...

剪接功能參數(shù)可以讓人很依賴。取代 [a..., b...] 這種寫法,簡單的 [a, b] 這樣寫就已經(jīng)連接數(shù)組了。collect(a) 的寫法要比 [a...] 好,但是因?yàn)?a 已經(jīng)是可迭代的了,直接用 a 而不要把它轉(zhuǎn)換到數(shù)組中也許會(huì)更好。

不要使用不必要的靜態(tài)參數(shù)

信號函數(shù):

    foo{T<:Real}(x::T) = ...

應(yīng)該這樣寫:

    foo(x::Real) = ...

特別是如果 T 沒被用在函數(shù)主體。即使 T 被用在函數(shù)主體了,如果方便的話也可以被 typeof(x) 替代。這在表現(xiàn)上并沒有什么差異。要注意的是,這不是對一般的靜態(tài)參數(shù)都要謹(jǐn)慎,只是在它們不會(huì)被用到時(shí)要特別留心。

還要注意容器類型,特別是函數(shù)調(diào)用中可能需要的類型參數(shù)??梢缘?FAQ 如何聲明“抽象容器類型”的域 來查看更多信息。

避免對實(shí)例或類型判斷的困擾

一些如以下的定義是十分讓人困擾的:

    foo(::Type{MyType}) = ...
    foo(::MyType) = foo(MyType)

您要決定問題的概念是應(yīng)被寫作 MyType 或是 MyType(),并要堅(jiān)持下去。

最好的類型是用默認(rèn)的實(shí)例,并且在解決某些問題需要方法時(shí),再添加包括 Type{MyType} 的一些方法好一些。

如果一個(gè)類型是一個(gè)有效的枚舉,它就應(yīng)該被定義為一個(gè)單一的(理想情況下不變的)類型,而枚舉變量是它的實(shí)例。構(gòu)造函數(shù)和一些轉(zhuǎn)換可以檢測值是否有效。這項(xiàng)設(shè)計(jì)最好把枚舉做成抽象類型,把“值”做成其子類型。

不要濫用 macros

您要注意什么時(shí)候一個(gè) macros 可以真的代替函數(shù)。

在 macros 中調(diào)用 eval 實(shí)在是個(gè)危險(xiǎn)的標(biāo)志;這意味著 macros 只有在被最高級調(diào)用的時(shí)候才會(huì)工作。如果這樣一個(gè) macros 被寫為一個(gè)函數(shù),它將自然地訪問它需要的運(yùn)行時(shí)值。

不要在接口層暴露不安全的操作

如果您有一個(gè)使用本地指針的類型:

    type NativeType
        p::Ptr{Uint8}
        ...
    end

不要像下面這樣寫定義:

    getindex(x::NativeType, i) = unsafe_load(x.p, i)

問題是,這種類型的用戶可能在不知道該操作是不安全的情況下就寫 [i],這容易導(dǎo)致內(nèi)存錯(cuò)誤。

這樣的函數(shù)應(yīng)該能檢查操作,以確保它是安全的,或是在它的名字中有不安全的地方時(shí)可以提醒調(diào)用程序。

不要重載基容器類型的方法

像下面這樣書寫定義是有可能的:

    show(io::IO, v::Vector{MyType}) = ...

這樣寫將提供一個(gè)特定新元素類型的向量的自定義顯示。雖然很讓人想嘗試,但卻是應(yīng)該避免的。麻煩的是,用戶會(huì)想用一個(gè)眾所周知的類型比如向量在一個(gè)特定的方式下的行為,也會(huì)過度定制它的行為,這都會(huì)使工作更困難。

注意類型的相等性

您一般要使用 isa<: (issubtype) 來測試類型而不會(huì)用 ==。在與已知的具體類型的類型進(jìn)行比較時(shí),要精確檢查類型的的相等性(例如 T == Float64),或者是您真的明白您究竟在干什么。

不要寫 x->f(x)

高階函數(shù)經(jīng)常被用作匿名函數(shù)來調(diào)用,雖然這樣很方便,但是盡量少這么寫。例如,盡量把 map(x->f(x), a) 寫成 map(f, a)