调度简介

我们可以使用::语法来调度参数的类型

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 中则不是这样; 只有类型稳定性对性能很重要。