我正在阅读有关Julia语言的类型(通常用于计算科学): https://docs.julialang.org/en/v1/manual/types/
在"抽象类型"标题下,我读到"抽象类型不能实例化"。作者解释说,抽象类型只是用来定义什么是其他事物的超类型。"好吧,这很有用,一切都很好"。但是,在标题"Type{T}类型选择器"下,我读到«Type{T}是一个抽象的参数类型,其唯一的实例是对象T»。这怎么可能?
因此,作者首先说,"抽象类型的一个定义特征是它不能被实例化",这是真的。然后作者说同样的陈述是错误的(因为T是抽象类型Type{T}的实例)。作者这些看似矛盾的说法是什么意思?
我在这里写的不是 Julia 核心开发人员的官方解释。
首先让我们了解Type{T}
是什么,因为它是非标准的(这就是为什么在 Julia 手册中有一个单独的部分来介绍它)。我使用Int
作为T
具体来说。
第一:
julia> isabstracttype(Type{Int})
true
julia> isconcretetype(Type{Int})
false
这意味着Type{Int}
被朱莉娅认为是抽象的而不是具体的。
在某些依赖于这两个函数的代码中,此注意事项是相关的(很少见,但可能)。
现在Type{Int}
有什么特别之处?请看以下内容:
julia> typeof(Int)
DataType
julia> Type{Int} <: DataType
true
julia> DataType <: Type{Int}
false
julia> supertype(Type{Int})
Any
julia> isabstracttype(DataType)
false
julia> isconcretetype(DataType)
true
我们在这里看到了什么:
- 如果检查
Int
类型,则为DataType
。DataType
是具体的,而不是抽象的(与Type{Int}
相反)。 - 虽然
DataType
是具体的而不是抽象的,但它有一个子类型,例如Type{Int}
. - 尽管
Type{Int}
是DataType
的子类型,但如果要检查它,它的超类型是Any
。
这不是故事的结局。现在检查没有参数的Type
:
julia> supertype(DataType)
Type{T}
julia> subtypes(Type{T} where T)
4-element Vector{Any}:
Core.TypeofBottom
DataType
Union
UnionAll
julia> supertype(DataType)
Type{T}
julia> subtypes(Type{T} where T)
4-element Vector{Any}:
Core.TypeofBottom
DataType
Union
UnionAll
julia> Type{Union{Int, Missing}} <: DataType
false
julia> Type{Vector} <: DataType
false
julia> Type{Vector{Int}} <: DataType
true
我们在这里了解到的是Type
是DataType
的超型。此外,并非所有的"类型说明符"都是DataType
的子类型,因为Union
和UnionAll
类型不是DataType
。这些注意事项在更高级的通用 Julia 代码中非常重要。
现在让我们讨论一下 Julia 手册使用的一个约定(即我讨论语句"要实例化"和"是一个实例"之间的区别):
- 当它说某种类型可以实例化时,这意味着给定类型有一些值; 您可以使用
typeof
检查此具体类型; - 当它说某个值是某种类型的实例时,它遵循规则(我逐字复制它,因为所有写的东西都是相关的 - 本质上一个值可以是抽象类型的实例,也可以是具体类型的实例):
当附加到计算值的表达式时,:: 运算符被读作"is a instance"。可以在任何地方使用它来断言左侧表达式的值是右侧类型的实例。当右侧的类型是具体的时,左侧的值必须具有该类型作为其实现 - 回想一下,所有具体类型都是最终的,因此没有实现是任何其他实现的子类型。当类型是抽象的时,值由作为抽象类型的子类型的具体类型实现就足够了。
总结。在 Julia 中,需要添加一种方便的方式来传递类型作为值。我举两个例子:
julia> parse(Int, "12")
12
julia> rand(Int)
-2055168985030383807
因此,决定的设计是:
- 使每个类型都成为抽象
Type
类型的实例; DataType
捕获显式声明的类型,如Int
,但它不捕获Union
或UnionAll
之类的东西;每个显式声明的类型都是DataType
的实例- 然而,
DataType
在许多情况下(特别是对于调度)过于模糊,此外,正如我们上面所说,它没有涵盖所有可能的类型规范;因此需要引入Type{T}
,即向Type
添加参数;它是特殊的,这就是为什么Julia手册称它为Type{T}
类型选择器(因为它的用途是类型选择器)。
为了满足所有这些要求:
Int
是DataType
的实例,所以DataType
是具体的;- 同时我们希望
Int
成为Type{Int}
的实例;因为Type{Int}
不可能是DataType
的超类型,所以它必须是它的子类型,尽管DataType
是具体的;因此它必须是抽象的,正如Julia手册所说:"具体类型不能相互子类型"(即具体类型不能有具体的子类型;实际上后来它还说"所有具体类型都是最终的。并且可能只有抽象类型作为它们的超类型",这似乎表明它不能有子类型;但实际上Type{Int}
等是一个例外——即DataType
允许具有抽象子类型;但是 Julia 确保不可能创建Type{Int}
的具体子类型,因此主语句成立 - 不可能创建DataType
的具体子类型 )
我同意这有点"玩文字游戏",但在实践中,拥有Type{T}
是有用的,并且由于我上面描述的规则,它不会引入不一致。
说了这么多,让我参考一下你的陈述:
«抽象类型无法实例化»
这是真的,例如Type{Int}
不能实例化,因为它是抽象的。
«Type{T} 是一个抽象的参数类型,其唯一的实例是对象 T»
这也是正确的,因为值可以是抽象类型的实例,例如Int
是Type{Int}
的实例,就像Julia中的任何值都是Any
的实例一样。