Julia语言程序设计
上QQ阅读APP看书,第一时间看更新

6.2 抽象类型

如上面所述,Julia表达整数有多种不同结构,即Int8、UInt8、Int64、UInt64等,都是整型的一种。而整型一词便是这些具体整数类型的抽象,是一种统一性的描述。浮点型、实数型等同理。

作为以数值计算为特色的Julia语言,为了描述方便,建立与数学概念对等的概念,在语言层面为整型这种抽象概念提供了具象化的机制。我们可以通过Julia的语法规则明确地定义出类似于整型、浮点型、实数型,甚至是数字型等抽象类型。

在Julia中,抽象类型使用abstract type关键字声明,一般语法为:


abstract type name end

其中,name为定义的抽象类型名称。例如:


abstract type Number end

便定义了一个名为Number的抽象类型,表示某个数据为数字。

不过在现实中,概念的抽象是可以延伸的,也会有很多层次,比如整数型到实数型,实数型到数字型,等等。为了表达这种抽象的层次结构,表述子集与超集关系,Julia提供了标识符<:用于说明类型之间的包含与继承关系,而且这种关系可以在声明类型直接提供,即:


abstract type name <: supertype end

该声明规则中的name的上一级抽象类型是supertype类型。可以说,supertype的概念比name的层次更高,是name的超集,所以也称其为name的父类型;而name是supertype的子集,称为supertype的子类型。两者之间存在着明确的类型继承关系。例如:


abstract type Real <: Number end

表示实数Real类型是Number类型的子类型,是数字的一种。

如前所述,抽象类型只是描述同类具体类型的抽象概念,是一种语言机制,并没有明确的二进制内存结构与之关联,所以无法实例化,不能定义出拥有实际资源的对象。不过因为抽象类型实际是具体类型的超集,所以仍可在其上定义各种运算和操作,而且这些操作能够被其子类型继承复用。这其实与数学中空间域的性质是相通的,可以相互借鉴方便理解。

实际上,Julia在数值方面已经内置定义了较为完整的抽象类型,部分如下所示:


abstract type Number end
abstract type Real          <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer       <: Real end
abstract type Signed        <: Integer end
abstract type Unsigned      <: Integer end

其中,除了Number与Real之外,还定义了前文未提及的AbstractFloat类型,用于描述所有的浮点类型;还有表达整数类型的Integer类型,并且分为Signed与Unsigned两种,分别指有符号整型及无符号整型;而Integer及AbstractFloat都是Real的子类型。

另外,Julia还内置了特殊的抽象类型Any,即“任意类型”,位于类型继承关系的顶部,覆盖了Julia中声明定义的所有类型,是所有类型的父类型,所以Any也是Number的父类型。

如果我们将包括Any在内的各种数字抽象类型的继承关系绘制成图,便会得到如图6-1所示的树状结构。

图6-1 数字抽象类型拓扑树

从抽象类型的声明方式及图中,我们显然能够发现这种类型继承树是一种单继承树。在这棵树中,每个类型的上一级抽象类型只有一个,即有且只有父类型节点;同时,除了叶子类型外,其他都会有子类型,而且某些会有多个子类型,例如Real类型下至少有AbstractFloat及Integer两种;而同属于某一层父类型的各子类型称为兄弟类型。这种依据Julia类型声明规则形成的层次性继承关系树,构成了Julia类型系统的重要基础—类型拓扑图(Type Graph)。

在这样的拓扑结构中,节点中的任一个类型向上追溯,只会找到唯一的路径能最终到达顶层的Any节点。而且,处于同一路径上的任两个类型都存在父子继承关系。

在图6-1的类型拓扑树中,虽然Real不是Signed的直接父节点,但仍可称Real是Signed的父类型,或称Signed是Real的子类型,对于其他类型关系亦是如此。如此一来,如果我们在Real层面定义了某个操作或运算,会被其下的所有子类型所遵循。但如果某个运算定义在Integer级别,便不能被“旁路”的AbstractFloat等类型遵循,因为它们之间不存在父子继承关系,没有与Integer处于到Any节点的同一路径上。兄弟类型共享的只能是共同父类型上定义的计算规则,这也是Julia中强大的多态分发机制的基础。

类型间的继承关系在C++、Java语言中也广泛存在。但C++中的多继承很容易被滥用,不但增加了维护难度,降低运行性能,还很容易引发其他各种不可预期的问题。Julia采用单继承方式,正是吸取了这方面的教训。也正因为如此,Julia的类型系统有着更为强大的生命力,不但更为实用、直观、清晰,而且简洁、更容易表达,为其他各种强大的功能奠定了坚实的基础。