为什么要在 Julia 中创建抽象超类型?
Why create an abstract super type in Julia?
我正在查看某人的代码,它在创建结构时使用 <:
。我查看了 docs 并发现这意味着他们正在创建一个抽象超类型。谁能解释这是什么以及为什么要使用它?
来自 https://docs.julialang.org/en/v1/manual/types/ 的文档:
“Julia 类型系统的一个特别显着的特征是具体类型不能相互子类型:所有具体类型都是最终类型,并且只能将抽象类型作为它们的超类型。虽然乍一看这似乎限制过度,但它已经许多有益的结果和令人惊讶的很少的缺点。事实证明,能够继承行为比能够继承结构重要得多,而继承两者会给传统的面向对象语言带来重大困难...
因此,当您看到“<: 创建结构时”时,您所看到的是从抽象类型继承结构的示例,通常是为了使用新的该类型的其他函数(方法)结构。也就是说,代码没有创建抽象超类型。 <: 意味着他们正在创建一个具体类型,但是(使用 <:)从先前声明的抽象类型(在那种情况下应该在 <: 的右边)派生它。
什么是抽象类型?
抽象类型是类型层次结构中的节点:它们将类型组合在一起。这允许您编写适用于整个类型组的方法:
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!"
为什么抽象类型有用?
抽象类型允许您将行为与实现分开。这对性能至关重要。当您声明一个抽象超类型时,您会自动继承超类型的核心行为,但可以自由地实现该行为的更高效实现。
一个常见的例子是AbstractArray
抽象类型。它表示访问某些多维元素集合的单个元素的能力。给定一些问题,我们通常可以选择抽象数组的子类型,这将产生高效的操作:子类型的附加约束构成了程序员可以利用的信息来提高某些操作的效率。
例如,假设我们要求 1..N 的和。我们可以使用一个整数数组,但与 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
BitArray
为布尔数组提供 space 高效表示,SparseArrays
为稀疏数据提供高效操作,等等。如果您有一些数据通常表现得像抽象数组,但具有独特的特征,您可以定义自己的子类型。
此模式推广到其他抽象类型。使用它们对某些共享行为的不同实现进行分组。
一个更实际的用例是创建强类型的、可能相互递归的结构。例如,您不能编写以下内容:
struct Node
edges::Vector{Edge}
end
struct Edge
from::Node
to::Node
end
一种写法是相当人为的
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
通常,有了足够的经验,这个问题会在数据结构的设计过程中自然而然地解决,如下所示:
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
这确保 Expression
s(求值为数字的东西)和 Statement
s(只是副作用的东西)被正确地分开——你永远不能创建无效的程序,比如1 + (x = 2)
。如果没有抽象类型(或相互递归类型,但它们目前 do not exist)就无法编写它。
我正在查看某人的代码,它在创建结构时使用 <:
。我查看了 docs 并发现这意味着他们正在创建一个抽象超类型。谁能解释这是什么以及为什么要使用它?
来自 https://docs.julialang.org/en/v1/manual/types/ 的文档:
“Julia 类型系统的一个特别显着的特征是具体类型不能相互子类型:所有具体类型都是最终类型,并且只能将抽象类型作为它们的超类型。虽然乍一看这似乎限制过度,但它已经许多有益的结果和令人惊讶的很少的缺点。事实证明,能够继承行为比能够继承结构重要得多,而继承两者会给传统的面向对象语言带来重大困难...
因此,当您看到“<: 创建结构时”时,您所看到的是从抽象类型继承结构的示例,通常是为了使用新的该类型的其他函数(方法)结构。也就是说,代码没有创建抽象超类型。 <: 意味着他们正在创建一个具体类型,但是(使用 <:)从先前声明的抽象类型(在那种情况下应该在 <: 的右边)派生它。
什么是抽象类型?
抽象类型是类型层次结构中的节点:它们将类型组合在一起。这允许您编写适用于整个类型组的方法:
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!"
为什么抽象类型有用?
抽象类型允许您将行为与实现分开。这对性能至关重要。当您声明一个抽象超类型时,您会自动继承超类型的核心行为,但可以自由地实现该行为的更高效实现。
一个常见的例子是AbstractArray
抽象类型。它表示访问某些多维元素集合的单个元素的能力。给定一些问题,我们通常可以选择抽象数组的子类型,这将产生高效的操作:子类型的附加约束构成了程序员可以利用的信息来提高某些操作的效率。
例如,假设我们要求 1..N 的和。我们可以使用一个整数数组,但与 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
BitArray
为布尔数组提供 space 高效表示,SparseArrays
为稀疏数据提供高效操作,等等。如果您有一些数据通常表现得像抽象数组,但具有独特的特征,您可以定义自己的子类型。
此模式推广到其他抽象类型。使用它们对某些共享行为的不同实现进行分组。
一个更实际的用例是创建强类型的、可能相互递归的结构。例如,您不能编写以下内容:
struct Node
edges::Vector{Edge}
end
struct Edge
from::Node
to::Node
end
一种写法是相当人为的
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
通常,有了足够的经验,这个问题会在数据结构的设计过程中自然而然地解决,如下所示:
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
这确保 Expression
s(求值为数字的东西)和 Statement
s(只是副作用的东西)被正确地分开——你永远不能创建无效的程序,比如1 + (x = 2)
。如果没有抽象类型(或相互递归类型,但它们目前 do not exist)就无法编写它。