抽象类型是类型层次结构中的节点:它们将类型组合在一起。这允许您编写适用于整个类型组的方法:
julia> abstract type AbstractFoo end
julia> struct Foo1 <: AbstractFoo end
julia> struct Foo2 <: AbstractFoo end
julia> foo_op(x::AbstractFoo) = "yay!"
foo_op (generic function with 1 method)
julia> foo_op(Foo1())
"yay!"
julia> foo_op(Foo2())
"yay!"
Run Code Online (Sandbox Code Playgroud)
抽象类型允许您将行为与实现分开。这对性能至关重要。当你声明一个抽象超类型时,你会自动继承超类型的核心行为,但可以自由地实现该行为的更有效的实现。
一个常见的例子是AbstractArray抽象类型。它代表了访问某些多维元素集合的单个元素的能力。给定一些问题,我们通常可以选择抽象数组的子类型,这将产生高效的操作:子类型上的附加约束构成了程序员可以利用的信息来使某些操作更高效。
例如,假设我们要找到 1..N 的总和。我们可以使用整数数组,但这与 a 相比效率非常低UnitRange。UnitRange关于数据特征的编码信息的选择;我们可以利用信息来提高效率。(有关此示例的更多信息,请参阅此答案)。
julia> using BenchmarkTools
julia> @btime sum($(1:1000_000))
0.012 ns (0 allocations: 0 bytes)
500000500000
julia> @btime sum($(collect(1:1000_000)))
229.979 ?s (0 allocations: 0 bytes)
500000500000
Run Code Online (Sandbox Code Playgroud)
BitArray为布尔数组提供空间高效的表示,SparseArrays为稀疏数据提供高效的操作,等等。如果您有一些数据通常表现得像一个抽象数组,但具有独特的特征,您可以定义自己的子类型。
这种模式推广到其他抽象类型。使用它们来对某些共享行为的不同实现进行分组。
一个更实际的用例是创建强类型的、可能相互递归的结构。例如,您不能编写以下内容:
struct Node
edges::Vector{Edge}
end
struct Edge
from::Node
to::Node
end
Run Code Online (Sandbox Code Playgroud)
写这个的一种方法是相当人为的
abstract type AbstractNode end
abstract type AbstractEdge end
struct Node{E<:AbstractEdge}
edges::Vector{E}
end
struct Edge{N<:AbstractNode}
from::N
to::N
end
Run Code Online (Sandbox Code Playgroud)
通常,有了足够的经验,这个问题在数据结构的设计过程中自然就已经解决了,如下所示:
abstract type Program end
abstract type Expression <: Program end
abstract type Statement <: Program
struct Literal <: Expression
value::Int
end
struct Var <: Expression
name::Symbol
end
struct Plus <: Expression
x::Expression
y::Expression
end
struct Assign <: Statement
var::Var
expr::Expression
end
struct Block <: Expression
side_effects::Vector{<:Program}
result::Expression
end
Run Code Online (Sandbox Code Playgroud)
这确保Expressions(计算为数字Statement的东西)和s(只是副作用的东西)被正确分开——你永远不能创建像1 + (x = 2). 如果没有抽象类型(或相互递归的类型,但它们目前不存在),就无法编写它。