Julia设计模式
上QQ阅读APP看书,第一时间看更新

2.6 使用参数化类型

Julia语言最强大的功能之一就是能够对类型进行参数化。实际上,很难找到不使用此功能的Julia包。参数化类型允许软件设计人员归纳类型,并让Julia运行时根据指定的参数自动编译为具体类型。

让我们看一下它如何与复合类型和抽象类型一起使用。

2.6.1 使用参数化复合类型

在设计复合类型时,我们应该为每个字段分配一个类型。只要类型可以提供我们所需的功能,我们就根本不会在意这些类型是什么。

一个经典的例子是数字类型。数字的概念很简单:与我们在小学时所教的基本相同。实际上,由于数据的不同的物理存储和数据表示,因此在计算机系统中实现了许多数字类型。

默认情况下,Julia附带如图2-7所示的数字类型(具体类型是深色底)。

图 2-7

你是否还记得我们在本章前面设计复合类型来代表投资组合中的股票?让我们在这里重新查看该示例:

如果我必须在经纪账户中持有一些股票,那么我还应该能追踪自己拥有的股票数量。为此,我定义一个名为StockHolding的新类型,如下所示:

默认情况下,Int数据类型的别名为Int64或Int32,具体取决于你使用的是Julia的64位版本还是32位版本。一开始这似乎很合理,但是如果我们需要在不同的用例中支持小数怎么办?在这种情况下,我们可以将quantity类型更改为Float64:

我们基本上将quantity字段的类型扩展为既支持整数值又支持浮点值的类型。这可能是一种合理的方法,但是如果我们需要同时支持Int和Float64类型,那么我们必须维护两个略有不同的类型。悲剧的是,如果我们确实创建了两种不同的类型,那么它将成为程序维护的噩梦。

为了使其更加灵活,我们可以使用参数重新设计StockHolding类型:

花括号内的符号T称为类型参数。它作为占位符,可以在任何字段中用作类型。现在两全其美。StockHolding{Int}类型是指包含Int类型的quantity字段的类型。同样,StockHolding{Float64}是指包含Float64类型的quantity字段的类型。

实际上,T类型参数只能是数字类型,因此我们可以进一步将T限定为Real的任何子类型:

这是我们的阅读方式——StockHolding类型包含一个股票和作为Real子类型的T类型的一个股权。这意味着我们可以创建一个quantity类型为Float16、Float32、Float64、Int8、Int16、Int32等的新StockHolding。让我们尝试使用不同类型的类型参数(例如Int、Float64和Rational)实例化StockHolding对象。

我们可以发现,根据传递给构造函数的参数自动创建了不同的StockHolding{T}类型。

参数类型的另一种用途是强制字段类型的一致性。假设我们要设计另一种所持股票对象来追踪所持股票的价格和市场价值。为了避免与前一个混淆,我们将其称为StockHolding2。下面是它的定义:

由于quantity的类型可能与price和marketvalue的类型不同,我们添加了一个新的类型参数P。现在,我们可以实例化一个StockHolding2对象,该对象包含整数的股票,同时具有价格和市场价值字段的浮点值。

请注意,如前面的截图所示类型为StockHolding2{Int64,Float64}。在这种情况下,类型参数T为Int64,参数P为Float64。

当我们声明price和marketvalue字段都必须是同一类型P时,Julia是否为我们强制执行此规则?试一试吧。

它的确强制执行了。我们正确地收到了一个错误,因为我们为price传递了Float64值,但为marketvalue传递了Int64。让我们仔细看看错误消息,该错误消息表明了系统的期望。StockHolding2的最接近候选函数的第三个和第四个参数采用P类型,其中P是AbstractFloat的任何子类型。因为In64不是AbstractFloat的子类型,所以没有匹配,因此引发了错误。

参数类型也可以是抽象的。接下来,我们将继续进行讨论。

2.6.2 使用参数化抽象类型

复合类型可以以参数化的方式进行增强,抽象类型同样也可以。让我们继续前面的例子,假设我们要构建一个名为Holding的抽象类型,该抽象类型跟踪其子类型使用的P类型。我们可以编写如下代码:

然后Holding{P}的每个子类型还必须带有一个P类型参数。例如,我们可以创建两个新类型StockHolding3{T,P}和CashHolding{P}:

我们可以检查这些类型之间的关系,如下所示。

我们再创建一个新的StockHolding3对象。

如我们期望的一样,certificate_in_the_safe对象是Holding{Float64}的子类型

请注意,在对类型进行参数化时,每个变体都被视为与其他变体无关的单独类型,只是它们具有共同的超类型。例如,Holding{Int}与Holding{Float64}是不同的类型,但是它们都是Holding的子类型。让我们快速证实一下。

总而言之,Julia附带了一个非常丰富的类型系统,开发人员可以使用它来推理每种类型与其他类型的关系。抽象类型使我们能够定义关系层次结构中的行为,而具体类型则用于定义数据的存储方式。参数化类型用于将现有类型扩展为字段类型的变体。所有这些语言构造都使开发人员可以有效地对数据和行为进行建模。

接下来,我们将研究数据类型转换及它如何应用于函数。