调度简介
我们可以使用::
语法来调度参数的类型 。
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
范围内的数字执行此操作,但如有必要,我们将允许使用不同的值。我们还将允许不同的短语用于 Fizz
或 Buzz
。
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}
,因为它只能包含 Int
s,而 Vector{Number}
可以包含任何类型的数字。
编写通用代码
Dispatch 是一个非常强大的功能,但通常最好编写适用于所有类型的通用代码,而不是为每种类型专门编写代码。编写通用代码可避免代码重复。
例如,这里是计算整数向量的平方和的代码:
function sumsq(v::Vector{Int})
s = 0
for x in v
s += x ^ 2
end
s
end
但是此代码仅适用于 Int
s 的矢量。它不适用于 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 中则不是这样; 只有类型稳定性对性能很重要。