Julia调度介绍

例子

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

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

如果我们fizzbuzz在 REPL 中检查,它会说有四种方法。为每个允许的参数组合创建一个方法。

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}or 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 中并非如此;只有类型稳定性对性能很重要。