以下各節(jié)從幾方面介紹了符合語(yǔ)言習(xí)慣的 Julia 編碼風(fēng)格。這些規(guī)則都不是絕對(duì)的;它們僅僅是幫您熟悉這門(mén)語(yǔ)言,或是幫您可以在許多可替代性設(shè)計(jì)中能夠做出選擇的一些建議而已。
編寫(xiě)代碼作為在一系列步驟中最高級(jí)的辦法,是可以快速開(kāi)始解決問(wèn)題的,但您應(yīng)該試著盡快把一個(gè)程序分成許多函數(shù)。函數(shù)具有更好的可重用性和可測(cè)試性,并可以更好闡明它們正在做什么,它們的輸入和輸出是什么。此外,由于 Julia 的編譯器工作原理,在函數(shù)中的代碼往往比最高級(jí)別的代碼運(yùn)行得更快。
同樣值得強(qiáng)調(diào)的是,函數(shù)應(yīng)該以參數(shù)來(lái)代替,而不是直接在全局變量(除了像 pi 那樣的常量)上操作。
代碼應(yīng)盡可能通用。相較于這樣的代碼書(shū)寫(xiě):
convert(Complex{Float64}, x)
使用有效的泛型函數(shù)是更好的:
complex(float(x))
第二種寫(xiě)法把 x
轉(zhuǎn)換成一個(gè)適當(dāng)?shù)念愋?,而不是一直用一個(gè)相同的類型。
這種類型特點(diǎn)是特別地與函數(shù)自變量相關(guān)。例如,不聲明一個(gè)參數(shù)是 Int
類型或 Int32
類型,如果在這種情況下還可以保持是任何整數(shù),那就應(yīng)該是用 Integer
抽象表達(dá)出來(lái)的。事實(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ù)的問(wèn)題。關(guān)鍵是要意識(shí)到,僅僅是定義通用的 addone(x) = x + one(x)
寫(xiě)法也是沒(méi)有性能缺失的,因?yàn)?Julia 會(huì)根據(jù)需要自主編譯到專業(yè)的版本。舉個(gè)例子,您第一次調(diào)用 addone(12)
的時(shí)候, Julia 會(huì)自動(dòng)為 x::Int
自變量編譯一個(gè) addone
函數(shù),通過(guò)調(diào)用一個(gè)內(nèi)聯(lián)值 1
代替 one
。因此,上表前三個(gè)定義全都是重復(fù)的。
取代這種寫(xiě)法:
function foo(x, y)
x = int(x); y = int(y)
...
end
foo(x, y)
利用以下的寫(xiě)法更好:
function foo(x::Int, y::Int)
...
end
foo(int(x), int(y))
第二種寫(xiě)法更好的方式,因?yàn)?foo
并沒(méi)有真正接受所有類型的數(shù)據(jù);它真正需要的是 Int
S。
這里的一個(gè)問(wèn)題是,如果一個(gè)函數(shù)本質(zhì)上需要整數(shù),可能更好的方式是強(qiáng)制調(diào)用程序來(lái)決定怎樣轉(zhuǎn)換非整數(shù)(例如最低值或最高值)。另一個(gè)問(wèn)題是,聲明更具體的類型會(huì)為未來(lái)的方法定義提供更多的“空間”。
取代這種寫(xiě)法:
function double{T<:Number}(a::AbstractArray{T})
for i = 1:endof(a); a[i] *= 2; end
a
end
利用以下寫(xiě)法更好:
function double!{T<:Number}(a::AbstractArray{T})
for i = 1:endof(a); a[i] *= 2; end
a
end
Julia 標(biāo)準(zhǔn)庫(kù)在整個(gè)過(guò)程中使用以上約定,并且 Julia 標(biāo)準(zhǔn)庫(kù)還包含一些函數(shù)復(fù)制和修飾形式的例子(例如 sort
和 sort!
),或是其它只是在修飾(例如 push!
, pop!
,splice!
)的例子。這對(duì)一些也要為了方便而返回修改后數(shù)組的函數(shù)來(lái)說(shuō)是很典型的。
像 Union(Function,String)
這樣的類型,說(shuō)明你的設(shè)計(jì)有問(wèn)題。
當(dāng)使用 x::Union(Nothing,T)
時(shí),想想把 x
轉(zhuǎn)換成 nothing
這個(gè)選項(xiàng)是否是必要的。以下是一些可供選擇的替代選項(xiàng)
x
一起初始化x
的類型x
的域,就把它們存儲(chǔ)在字典中x
是 noting
時(shí)是否有一個(gè)簡(jiǎn)單的規(guī)則。例如,域通常是以 nothing
開(kāi)始的,但是是在一些定義良好的點(diǎn)被初始化。在這種情況下,要首先考慮它可能沒(méi)被定義。通常情況下,像下面這樣創(chuàng)建數(shù)組是沒(méi)什么幫助的:
a = Array(Union(Int,String,Tuple,Array), n)
在這種情況下 cell(n)
這樣寫(xiě)更好一些。 這也有助于對(duì)編譯器進(jìn)行注釋這一特定用途,而不是試圖將許多選擇打包成一種類型。
base/
相同的命名傳統(tǒng)module SparseMatrix
,
immutable UnitRange
.maximum
, convert
). 在容易讀懂的情況下把幾
個(gè)單詞連在一起寫(xiě) (isequal
, haskey
). 在必要的情況下, 使用下劃
線作為單詞的分隔符. 下劃線也可以用來(lái)表示多個(gè)概念的組合
(remotecall_fetch
相比 remotecall(fetch(...))
是一種更有效的
實(shí)現(xiàn)), 或者是為了區(qū)分 (sum_kbn
). 簡(jiǎn)潔是提倡的, 但是要避免縮寫(xiě)
(indexin
而不是 indxin
) 因?yàn)楹茈y記住某些單詞是否縮寫(xiě)或者怎么
縮寫(xiě)的.如果一個(gè)函數(shù)需要多個(gè)單詞來(lái)描述, 想一下這個(gè)函數(shù)是否包含了多個(gè)概念, 這樣 的情況下最好分拆成多個(gè)部分.
避免錯(cuò)誤要比依賴找錯(cuò)好多了。
Julia 在 if 和 while 語(yǔ)句中不需要括號(hào)。所以要這樣寫(xiě):
if a == b
來(lái)取代:
if (a == b)
剪接功能參數(shù)可以讓人很依賴。取代 [a..., b...]
這種寫(xiě)法,簡(jiǎn)單的 [a, b]
這樣寫(xiě)就已經(jīng)連接數(shù)組了。collect(a)
的寫(xiě)法要比 [a...]
好,但是因?yàn)?a
已經(jīng)是可迭代的了,直接用 a
而不要把它轉(zhuǎn)換到數(shù)組中也許會(huì)更好。
信號(hào)函數(shù):
foo{T<:Real}(x::T) = ...
應(yīng)該這樣寫(xiě):
foo(x::Real) = ...
特別是如果 T 沒(méi)被用在函數(shù)主體。即使 T 被用在函數(shù)主體了,如果方便的話也可以被 typeof(x) 替代。這在表現(xiàn)上并沒(méi)有什么差異。要注意的是,這不是對(duì)一般的靜態(tài)參數(shù)都要謹(jǐn)慎,只是在它們不會(huì)被用到時(shí)要特別留心。
還要注意容器類型,特別是函數(shù)調(diào)用中可能需要的類型參數(shù)??梢缘?FAQ 如何聲明“抽象容器類型”的域 來(lái)查看更多信息。
一些如以下的定義是十分讓人困擾的:
foo(::Type{MyType}) = ...
foo(::MyType) = foo(MyType)
您要決定問(wèn)題的概念是應(yīng)被寫(xiě)作 MyType
或是 MyType()
,并要堅(jiān)持下去。
最好的類型是用默認(rèn)的實(shí)例,并且在解決某些問(wèn)題需要方法時(shí),再添加包括 Type{MyType}
的一些方法好一些。
如果一個(gè)類型是一個(gè)有效的枚舉,它就應(yīng)該被定義為一個(gè)單一的(理想情況下不變的)類型,而枚舉變量是它的實(shí)例。構(gòu)造函數(shù)和一些轉(zhuǎn)換可以檢測(cè)值是否有效。這項(xiàng)設(shè)計(jì)最好把枚舉做成抽象類型,把“值”做成其子類型。
您要注意什么時(shí)候一個(gè) macros 可以真的代替函數(shù)。
在 macros 中調(diào)用 eval
實(shí)在是個(gè)危險(xiǎn)的標(biāo)志;這意味著 macros 只有在被最高級(jí)調(diào)用的時(shí)候才會(huì)工作。如果這樣一個(gè) macros 被寫(xiě)為一個(gè)函數(shù),它將自然地訪問(wèn)它需要的運(yùn)行時(shí)值。
如果您有一個(gè)使用本地指針的類型:
type NativeType
p::Ptr{Uint8}
...
end
不要像下面這樣寫(xiě)定義:
getindex(x::NativeType, i) = unsafe_load(x.p, i)
問(wèn)題是,這種類型的用戶可能在不知道該操作是不安全的情況下就寫(xiě) [i]
,這容易導(dǎo)致內(nèi)存錯(cuò)誤。
這樣的函數(shù)應(yīng)該能檢查操作,以確保它是安全的,或是在它的名字中有不安全的地方時(shí)可以提醒調(diào)用程序。
像下面這樣書(shū)寫(xiě)定義是有可能的:
show(io::IO, v::Vector{MyType}) = ...
這樣寫(xiě)將提供一個(gè)特定新元素類型的向量的自定義顯示。雖然很讓人想嘗試,但卻是應(yīng)該避免的。麻煩的是,用戶會(huì)想用一個(gè)眾所周知的類型比如向量在一個(gè)特定的方式下的行為,也會(huì)過(guò)度定制它的行為,這都會(huì)使工作更困難。
您一般要使用 isa
和 <:
(issubtype
) 來(lái)測(cè)試類型而不會(huì)用 ==
。在與已知的具體類型的類型進(jìn)行比較時(shí),要精確檢查類型的的相等性(例如 T == Float64
),或者是您真的明白您究竟在干什么。
x->f(x)
高階函數(shù)經(jīng)常被用作匿名函數(shù)來(lái)調(diào)用,雖然這樣很方便,但是盡量少這么寫(xiě)。例如,盡量把 map(x->f(x), a)
寫(xiě)成 map(f, a)
。
更多建議: