排程簡介

我們可以使用::語法來排程引數的型別

describe(n::Integer) = "integer $n"
describe(n::AbstractFloat) = "floating point $n"

用法:

julia> describe(10)
"integer 10"

julia> describe(1.0)
"floating point 1.0"

與許多通常提供靜態多重排程或動態單一排程的語言不同,Julia 具有完全動態的多重排程。也就是說,函式可以專用於多個引數。在為某些型別的操作定義專用方法時,這會派上用場,而對於其他型別則可以使用回退方法。

describe(n::Integer, m::Integer) = "integers n=$n and m=$m"
describe(n, m::Integer) = "only m=$m is an integer"
describe(n::Integer, m) = "only n=$n is an integer"

用法:

julia> describe(10, 'x')
"only n=10 is an integer"

julia> describe('x', 10)
"only m=10 is an integer"

julia> describe(10, 10)
"integers n=10 and m=10"

可選引數

Julia 允許函式採用可選引數。在幕後,這是作為多個排程的另一個特例實現的。例如,讓我們解決流行的 Fizz Buzz 問題 。預設情況下,我們將對 1:10 範圍內的數字執行此操作,但如有必要,我們將允許使用不同的值。我們還將允許不同的短語用於 FizzBuzz

function fizzbuzz(xs=1:10, fizz="Fizz", buzz="Buzz")
    for i in xs
        if i % 15 == 0
            println(fizz, buzz)
        elseif i % 3 == 0
            println(fizz)
        elseif i % 5 == 0
            println(buzz)
        else
            println(i)
        end
    end
end

如果我們在 REPL 中檢查 fizzbuzz,它說有四種方法。為每個允許的引數組合建立了一種方法。

julia> fizzbuzz
fizzbuzz (generic function with 4 methods)

julia> methods(fizzbuzz)
# 4 methods for generic function "fizzbuzz":
fizzbuzz() at REPL[96]:2
fizzbuzz(xs) at REPL[96]:2
fizzbuzz(xs, fizz) at REPL[96]:2
fizzbuzz(xs, fizz, buzz) at REPL[96]:2

我們可以驗證在沒有提供引數時使用我們的預設值:

julia> fizzbuzz()
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz

但如果我們提供可選引數,則接受並尊重它們:

julia> fizzbuzz(5:8, "fuzz", "bizz")
bizz
fuzz
7
8

引數排程

通常情況下,函式應排程引數型別,例如 Vector{T}Dict{K,V},但型別引數不固定。這種情況可以通過引數排程來處理:

julia> foo{T<:Number}(xs::Vector{T}) = @show xs .+ 1
foo (generic function with 1 method)

julia> foo(xs::Vector) = @show xs
foo (generic function with 2 methods)

julia> foo([1, 2, 3])
xs .+ 1 = [2,3,4]
3-element Array{Int64,1}:
 2
 3
 4

julia> foo([1.0, 2.0, 3.0])
xs .+ 1 = [2.0,3.0,4.0]
3-element Array{Float64,1}:
 2.0
 3.0
 4.0

julia> foo(["x", "y", "z"])
xs = String["x","y","z"]
3-element Array{String,1}:
 "x"
 "y"
 "z"

人們可能只想簡單地寫一下 xs::Vector{Number}。但這僅適用於型別明確為 Vector{Number} 的物件:

julia> isa(Number[1, 2], Vector{Number})
true

julia> isa(Int[1, 2], Vector{Number})
false

這是由於引數不變性 :物件 Int[1, 2] 不是 Vector{Number},因為它只能包含 Ints,而 Vector{Number} 可以包含任何型別的數字。

編寫通用程式碼

Dispatch 是一個非常強大的功能,但通常最好編寫適用於所有型別的通用程式碼,而不是為每種型別專門編寫程式碼。編寫通用程式碼可避免程式碼重複。

例如,這裡是計算整數向量的平方和的程式碼:

function sumsq(v::Vector{Int})
    s = 0
    for x in v
        s += x ^ 2
    end
    s
end

但是此程式碼適用於 Ints 的向量。它不適用於 UnitRange

julia> sumsq(1:10)
ERROR: MethodError: no method matching sumsq(::UnitRange{Int64})
Closest candidates are:
  sumsq(::Array{Int64,1}) at REPL[8]:2

它不適用於 Vector{Float64}

julia> sumsq([1.0, 2.0])
ERROR: MethodError: no method matching sumsq(::Array{Float64,1})
Closest candidates are:
  sumsq(::Array{Int64,1}) at REPL[8]:2

編寫這個 sumsq 函式的更好方法應該是

function sumsq(v::AbstractVector)
    s = zero(eltype(v))
    for x in v
        s += x ^ 2
    end
    s
end

這將適用於上面列出的兩種情況。但是在某種意義上,有些集合我們可能想要總結那些不是向量的正方形。例如,

julia> sumsq(take(countfrom(1), 100))
ERROR: MethodError: no method matching sumsq(::Base.Take{Base.Count{Int64}})
Closest candidates are:
  sumsq(::Array{Int64,1}) at REPL[8]:2
  sumsq(::AbstractArray{T,1}) at REPL[11]:2

表明我們不能對惰性迭代的平方求和。

更通用的實現很簡單

function sumsq(v)
    s = zero(eltype(v))
    for x in v
        s += x ^ 2
    end
    s
end

哪個適用於所有情況:

julia> sumsq(take(countfrom(1), 100))
338350

這是最慣用的 Julia 程式碼,可以處理各種情況。在其他一些語言中,刪除型別註釋可能會影響效能,但在 Julia 中則不是這樣; 只有型別穩定性對效能很重要。