调度类型

在 Julia 上,你可以为每个函数定义多个方法。假设我们定义了三个相同函数的方法:

foo(x) = 1
foo(x::Number) = 2
foo(x::Int) = 3

在决定使用什么方法(称为调度 )时,Julia 选择与参数类型匹配的更具体的方法:

julia> foo('one')
1

julia> foo(1.0)
2

julia> foo(1)
3

这有利于多态性 。例如,我们可以通过定义两个名为 NilCons 的不可变类型轻松创建链表 。这些名称传统上分别用于描述空列表和非空列表。

abstract LinkedList
immutable Nil <: LinkedList end
immutable Cons <: LinkedList
    first
    rest::LinkedList
end

我们将通过 Nil()Cons(first, rest) 的任何其他列表来表示空列表,其中 first 是链表的第一个元素,rest 是由所有剩余元素组成的链表。例如,列表 [1, 2, 3] 将表示为

julia> Cons(1, Cons(2, Cons(3, Nil())))
Cons(1,Cons(2,Cons(3,Nil())))

清单是空的吗?

假设我们想要扩展标准库的 isempty 函数,该函数适用于各种不同的集合:

julia> methods(isempty)
# 29 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
...

我们可以简单地使用函数调度语法,并定义 isempty 的另外两种方法。由于此功能来自 Base 模块,因此我们必须将其限定为 Base.isempty 才能扩展它。

Base.isempty(::Nil) = true
Base.isempty(::Cons) = false

在这里,我们根本不需要参数值来确定列表是否为空。仅仅类型就足以计算该信息。如果我们不需要使用它们的值,Julia 允许我们省略参数的名称,只保留它们的类型注释。

我们可以测试我们的 isempty 方法是否有效:

julia> using Base.Test

julia> @test isempty(Nil())
Test Passed
  Expression: isempty(Nil())

julia> @test !isempty(Cons(1, Cons(2, Cons(3, Nil()))))
Test Passed
  Expression: !(isempty(Cons(1,Cons(2,Cons(3,Nil())))))

事实上,isempty 的方法数量增加了 14:

julia> methods(isempty)
# 31 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394

显然,确定链表是否为空是一个简单的例子。但它导致更有趣的事情:

这份清单有多长?

标准库中的 length 函数为我们提供了集合或某些可迭代的长度。有很多方法可以为链表实现 length。特别是,在 Julia 中使用 while 循环可能是最快且最节省内存的。但是要避免过早优化 ,所以让我们假设我们的链表无需高效。编写 length 函数最简单的方法是什么?

Base.length(::Nil) = 0
Base.length(xs::Cons) = 1 + length(xs.rest)

第一个定义很简单:空列表的长度为 0。第二个定义也很容易理解:为了计算列表的长度,我们计算第一个元素,然后计算列表其余部分的长度。我们可以像测试 isempty 一样测试这种方法:

julia> @test length(Nil()) == 0
Test Passed
  Expression: length(Nil()) == 0
   Evaluated: 0 == 0

julia> @test length(Cons(1, Cons(2, Cons(3, Nil())))) == 3
Test Passed
  Expression: length(Cons(1,Cons(2,Cons(3,Nil())))) == 3
   Evaluated: 3 == 3

下一步

这个玩具示例远非实现链接列表中所需的所有功能。例如,它缺少迭代接口。但是,它说明了如何使用 dispatch 编写简短明了的代码。