Julia参数化基本原理,如果我们查看声明的参数化抽象类型Pointy
的类型,会发现:
其中,Pointy的类型不是DataType而是UnionAll;而UnionAll的类型才是DataType;UnionAll的父类型正是酷客教程前文曾介绍过的Type{T}类型。
至此,我们已经遇到了Type{T}的四个主要子类型:DataType、Union、UnionAll
以及Core.TypeofBottom
类型。它们之间的typeof与supertype关系如图所示。图中实线箭头由父类型指向子类型;虚线由实例指向其类型。
由图可见,参数化的Type{T}
的类型是UnionAll, Union{}的类型是Core. TypeofBottom
;而余下的类型都是DataType,包括DataType自身。
事实上,Julia中的参数化类型都被处理为UnionAll的实例对象,其内部结构为:
julia> dump(UnionAll)
UnionAll <: Type{T}
var::TypeVar
body::Any
其内部有两个部分:一部分是类型为TypeVar的var,另一部分是Any类型的body。
为了方便说明,以其实例Pointy的内部结构为例,解释这种结构的含义,代码如下:
julia> dump(Pointy)
UnionAll
var: TypeVar
name: Symbol T
lb: Core.TypeofBottom Union{}
ub: Any
body: Pointy{T} <: Any
其中,body实际是Pointy的原型,而且TypeVar已被初始化,其name是声明时的类型参数T,但是其中的类型上下界均采用了默认值,即Any和Union{}类型。
如上文所述,TypeVar是可以设定上下界的。如果我们“粗暴”地直接修改Pointy的上界,然后再次查看其内部结构,会发现:
julia> Pointy.var.ub = Real;
julia> dump(Pointy)
UnionAll
var: TypeVar
name: Symbol T
lb: Core.TypeofBottom Union{}
ub: Real <: Number
body: Pointy{T<:Real} <: Any
其中,var.ub
变成了Real <: Number
,同时body内Pointy的原型变成了:
Pointy{T<:Real} <: Any
如果我们继续修改下界类型值为Integer,会发现Pointy的内部结构变成了:
julia> dump(Pointy)
UnionAll
var: TypeVar
name: Symbol T
lb: Integer <: Real
ub: Real <: Number
body: Pointy{Integer<:T<:Real} <: Any
在var中便意味着T的上下界分别被限定为Real与Integer类型。
如果此时再对Pointy具象化,会发现已不再是任意的类型都能作为其类参,例如:
julia> Pointy{Int64}
ERROR: TypeError: Pointy: in T, expected Integer<:T<:Real, got Type{Int64}
julia> Pointy{Complex}
ERROR: TypeError: Pointy: in T, expected Integer<:T<:Real, got Type{Complex}
并提示类型参数T只能是Integer的父类型,Real的子类型。
实际上,我们在声明Pointy时可直接将这种上下界限定关系明确地表示出来,即:
abstract type Pointy{Integer <: T <: Real} end
如果此时查看该新Pointy的内部结构,会发现:
UnionAll
var: TypeVar
name: Symbol T
lb: Integer <: Real
ub: Real <: Number
body: Pointy{Integer<:T<:Real} <: Any
与之前粗暴方式修改过的Pointy完全一致。
可见UnionAll类型通过将声明的类型原型及TypeVar作为成员,便能够实现对参数化类型衍生范围的控制;会基于TypeVar提供的约束说明,以类参有效变化范围中的各类型为基础将参数类型展开为一族具象类型,赋予了参数类型类似Union的功能;而其中元素类型便是具象类型族。一般而言,有多少类参便会生成多少TypeVar成员。
在声明参数化类型时,无论是复合类型、抽象类型或元类型,不放任参数T自由选择而给予更为确切的范围限制是非常实用的。例如,在实践中坐标点表达的是图像中的像素位置,要求坐标值为整型而且必须是正数;或是地理经纬度,一般取浮点值,一旦出现其他类型值,通常是无效坐标。如果我们在声明类型时便做出限制,就能够尽早发现异常的数据,确保后续处理尽可能少地出现不可预期的结果或错误。
但是,对于类参范围的限定最好不要通过示例这种粗暴方式(上文仅为示例方便),而应通过声明的方式进行。例如:
mutable struct Point{T1 <: Integer, T2<: AbstractFloat}
x::T1
y::T2
end
或
primitive type LittleInt{T<:Integer} 8 end
酷客网相关文章:
评论前必须登录!
注册