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

4.7 类型提升

本节因涉及尚未介绍的Julia类型系统、多态机制及其他内部原理,所以建议作为选读内容。

正如前文所述,在涉及多个类型互操作的过程中,比如累加或者其他算术运算,一般需要将这些值先转换为相同的类型,以保证它们具有一致的内存结构,同时目标类型也会选择“较大”的类型[1]。所谓“较大”,指的是该类型能够准确地容纳所有的操作数及计算结果,通常是这些操作数类型中表达能力最强的一个。例如浮点值3.56和整型值8在一起运算时,数值8会被提升为浮点型,因为整型没有小数部分,所以没有浮点型的表达力强。

这样的过程在Julia中称为“类型提升”(Promotion)。对于任意类型、数量的操作数,可以通过promote()函数将它们转为共同的类型。例如:


julia> promote(UInt8(1), Int32(1), UInt32(1))
(0x00000001, 0x00000001, 0x00000001)      # Tuple{UInt32,UInt32,UInt32}

julia> promote(Int32(1), Float32(1.0),Float16(1.0))
(1.0f0, 1.0f0, 1.0f0)                     # Tuple{Float32,Float32,Float32}

julia> promote(pi, 1//2)
(3.141592653589793, 0.5)                                                # Tuple{Float64,Float64}

julia> promote(Int64(1), 1//2)
(1//1, 1//2)                                                            # Tuple{Rational{Int64},Rational{Int64}}

julia> promote(1//2, Float32(1))
(0.5f0, 1.0f0)                 # Tuple{Float32,Float32}

julia> promote(1+2im, Float64(1))
(1.0 + 2.0im, 1.0 + 0.0im)     # Tuple{Complex{Float64},Complex{Float64}}

在这样的类型提升系统中,浮点数会被提升到更大的浮点类型;整型会被提升到更大的整型;整型与浮点型混合时,会被提升到足够大的浮点型以能容纳所有的值;整型与有理数混合时,会被提升到有理数类型;有理数与浮点数混合时,会被提升到浮点数;复数与实数混合时,会被提升到相近的复数类型。

可见提升过程并非像看起来那样是隐式、自动进行的,这是因为在Julia内部已经定义了大量的类型提升规则。在多类型混合计算时,Julia只是遵循了这些规则实施而已。事实上,Julia中有一种多态分发机制,能够根据参数列表的差异选择同名函数的不同实现方法。所以Julia对类型有着较为严格的控制,不会随意对类型进行自动转换,尤其是函数的输入参数。运算符是语法有些特别的函数对象,操作数便是其输入参数。所以运算符也遵循多态机制,不会自动进行参数类型的转换,所谓的自动提升只是因为有对应规则的存在。

如果因故需要自定义类型提升规则(比如声明了新的类型),则可以通过promote_rule()函数实现,例如:


promote_rule(::Type{Float64}, ::Type{Float32}) = Float64

表示当Float64与Float32混合计算时,目标类型采用Float64类型。如果运算中遇到了两者作为操作数的情况,Julia内部便会通过promote()函数依照上述规则,将操作数均转换为Float64类型,然后再进行计算得到最终的结果。在我们声明创建了自己的新类型时,可以通过上述这样的机制,将其对接到Julia的类型提升系统中。

因为Julia对类型的严格控制,所以若要针对各种具体类型重复地定义某个函数的实现方法,以多态的方式支持各种参数组合是极为麻烦的。不过,我们可以借助抽象类型的特点,并结合类型提升机制,简洁高效地实现这样的需求。例如:


+(x::Number, y::Number) = +(promote(x,y)...)
-(x::Number, y::Number) = -(promote(x,y)...)
*(x::Number, y::Number) = *(promote(x,y)...)
/(x::Number, y::Number) = /(promote(x,y)...)

其中,四则运算为了能够适应任意两个数值类型,在参数类型中限定为抽象类型,这样可以接收整型、浮点型、复数等类型作为参数,并在函数实现体中通过类型提升机制将它们进行统一处理。

本节涉及的抽象类型、多态分发机制、函数定义等概念均会在后续章节详细介绍。此处不作赘述,如果读者感兴趣,可以学习完后续内容再回顾本节,便能够领略到这种方案的灵活强大之处。

[1] 可通过内置的widen()函数获知某个类型的较大类型,例如widen(Int8)=Int32。