Julia参数化继承关系

Julia参数化继承关系,在Julia中,在声明参数化类型时,其父类型无须是参数化类型。典型的情况是复数型Complex有理数型Rational,它们都是参数化的复合类型,但其父类型分别是抽象类型Number与Real。不过,如果以某个已经定义的参数化类型作为父类型,类参之间便会存在依存关系,而且声明的方式也有别于普通的类型。

首先,将抽象类型参数化的各种基本语法总结如下:

abstract type 类型名 {T1, T2, ... } end
abstract type 类型名 {T1 <: 类参上界1, T2, …} end
abstract type 类型名 {T1 >: 类参下界1, T2, …} end
abstract type 类型名 {类参下界1 <: T1 <: 类参上界1, T2, …} end

为了方便讲述,上面的语法示例仅以首个类参给出边界限定的表达形式,依次是无边界限定、只上界限定、只下界限定与上下界均限定四种。

声明参数化的复合类型时,如果需要继承某个参数化的抽象类型,有两种基本语法

为了方便讲述,仍以首个类参示范限界的形式,如下:

[mutable] struct 类型名{T1, T2, ...} <: 父抽象类型名{T1, T2, ...}
 # 成员定义
end

[mutable] struct 类型名{T1 <: 类参上界,T2, …} <: 父抽象类型名{T1, T2, ...}
 # 成员定义
end

其中,mutable同上文,用于控制可变性,为可选关键字;类参的配置有如下几个方面的限制:

  • 父类型中类参数量需与本身类型一致,不可多不可少。
  • 父类型中类参名均需在复合类型的类参列表中存在。
  • 父类型类参中不能再有限界语法,仅需罗列类参名。

而且限界语法只能在复合类型自己的类参列表中实现。另外,复合类型的类参可以更多些,不需一定在父类型中存在,但其类参表必须包含父类型的所有类参名。我们举例如下:

abstract type PointWith{T1 <: Integer, T2 <: AbstractFloat} end
abstract type PointNone{T} end

mutable struct PlanePointA{T, V} <: PointWith{T, V}
  x::T
  y::T
  v::V
end

mutable struct PlanePointB{T <: Integer, V <: AbstractFloat} <: PointNone{T}
  x::T
  y::T
  v::V
end

其中,声明了两个参数化的抽象类型,并用两种方式分别定义了它们的子类型PlanePointAPlanePointB。如果分别对PlanePointAPlanePointB实例化,会发现:
Julia参数化继承关系

两种对象的成员v的参数值均是Int64类型,却都因无法满足类型约束创建对象失败。但错误信息是有差异的,这说明约束检查的节点是不同的。

前一种方法中虽然PlanePointA没有无限界语法,但其父抽象类型PointWith声明时已经作了限界,所以在建立继承关系时,该限界便会传递到新的子类型PlanePointA中,也会在实例化时起效。后一种方法中,父类型PointNone虽然未有限界,但PlanePointB本身对类型进行了限界操作,所以创建的对象亦需满足约束。

之所以错误不同,因为前者在首先检查父类型类参约束时便发现提供的值不符合要求,而后者在调用构造方法时发现不存在匹配实参值组合的对象创建方法。以笔者之见,更推荐前一种用法,一是可以提前发现问题,二是对错误的定位更准确。

不过还有一种情况:前一种用法中,父抽象类型已经限界,复合类型仍在声明时限定了类参的上下界,例如:

mutable struct PlanePointC{T <: Signed, V <: Real} <: PointWith{T, V}
    x::T
    y::T
    v::V
  end

其中类参T在复合类型中“变小”了,而类参V变得“更大”了。如果对其实例化,会发现:

julia> PlanePointC(1, 1, 2)
  ERROR: TypeError: PointWith: in T2, expected T2<:AbstractFloat, got Type{Int64}

可见,虽然成员v的类型被复合类型放大,但父类型的约束仍在起作用。再例如:

julia> PlanePointC(UInt32(1), UInt32(1), 2.35)
  ERROR: MethodError: no method matching PlanePointC(::UInt32, ::UInt32, ::Float64)

其中,提供的x与y值均符合父类型的类型限定,但仍出错,因为没有找到匹配的构造方法。

实际上,目前的PlanePointC仅有一个构造方法,形如:

(::Type{PlanePointC})(x::T, y::T, v::V) where {T<:Signed, V<:Real}

其中,Type{PlanePointC}结构前文已经介绍过,用于指代名为PlanePointC的参数类型;参数表后的where其实是该参数化函数的表达方式,酷客教程后面会介绍。我们大致可看出其遵循了复合类型声明的限界规则,即构造方法是基于当前类型声明生成的。上例中提供的值并不符合要求,因为限定为Signed,但实际是UInt8类型,所以出现了无匹配方法的错误。可见,父类型与当前参数类型中的限界规则同时起效,并不是冲突,仅是约束节点不同;可以认为,总体上遵循最严格的那个限界要求。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!