Conversion and Promotion

Conversion and Promotion

Julia有一个用于将数学运算符的参数提升为通用类型的系统,在其他各个部分中都提到了该系统,包括整数和浮点数数学运算和基本函数类型方法 . 在本节中,我们将说明此提升系统如何工作,以及如何将其扩展为新类型并将其应用于除内置数学运算符之外的函数. 传统上,就促进算术参数而言,编程语言分为两个阵营:

从某种意义上说,Julia属于"没有自动提升"类别:数学运算符只是具有特殊语法的函数,而函数的参数永远不会自动转换. 但是,可能会观察到,将数学运算应用于多种混合参数类型只是多态多重分派的极端情况-Julia的分派和类型系统特别适合处理这种情况. 数学操作数的"自动"升级只是作为一种特殊应用出现:Julia附带了针对数学运算符的预定义全部捕获调度规则,当对操作数类型的某种组合不存在特定实现时调用. 这些通用规则首先使用用户可定义的提升规则将所有操作数提升为一个通用类型,然后为所得的值(现在是相同类型)调用所讨论运算符的专门实现. 用户定义的类型可以通过定义用于与其他类型之间进行转换的方法,并提供一些促销规则来定义它们与其他类型混合时应提升为哪种类型,从而轻松地参与此促销系统.

Conversion

获取特定类型T的值的标准方法是调用类型的构造函数T(x) . 但是,在某些情况下,无需程序员明确要求即可将值从一种类型转换为另一种类型很方便. 一个示例是将值分配给数组:如果AVector{Float64} ,则表达式A[1] = 2应该通过将2Int自动转换为Float64并将结果存储在数组中来工作. 这是通过convert功能完成的.

convert函数通常带有两个参数:第一个是类型对象,第二个是要转换为该类型的值. 返回的值是转换为给定类型的实例的值. 理解此功能的最简单方法是查看其功能:

julia> x = 12
12

julia> typeof(x)
Int64

julia> convert(UInt8, x)
0x0c

julia> typeof(ans)
UInt8

julia> convert(AbstractFloat, x)
12.0

julia> typeof(ans)
Float64

julia> a = Any[1 2 3; 4 5 6]
2×3 Array{Any,2}:
 1  2  3
 4  5  6

julia> convert(Array{Float64}, a)
2×3 Array{Float64,2}:
 1.0  2.0  3.0
 4.0  5.0  6.0

转换并非总是可能的,在这种情况下, convert引发没有方法错误,指示convert不知道如何执行请求的转换:

julia> convert(AbstractFloat, "foo")
ERROR: MethodError: Cannot `convert` an object of type String to an object of type AbstractFloat
[...]

某些语言认为将字符串解析为数字或将数字格式解析为要转换的字符串(许多动态语言甚至会自动为您执行转换),但是Julia却没有:即使某些字符串可以解析为数字,但大多数字符串都不是数字的有效表示形式数字,并且只有非常有限的一部分. 因此,在Julia中必须使用专用的parse函数来执行此操作,从而使其更加明确.

When is convert called?

以下语言构造了调用convert

Conversion vs. Construction

请注意, convert(T, x)的行为似乎与T(x)几乎相同. 确实,通常是这样. 但是,存在一个关键的语义差异:由于convert可以隐式调用,因此其方法仅限于被视为"安全"或"毫不奇怪"的情况. convert将仅在表示相同基本事物的类型之间进行转换(例如,不同的数字表示形式或不同的字符串编码). 通常它也是无损的. 将值转换为其他类型,然后再次返回将导致完全相同的值.

构造函数与convert不同的一般情况有四种:

Constructors for types unrelated to their arguments

一些构造函数没有实现"转换"的概念. 例如, Timer(2)创建一个2秒的计时器,这实际上不是从整数到计时器的"转换".

Mutable collections

如果x已经是T类型convert(T, x)期望返回原始x . 相反,如果T是可变的集合类型,则T(x)应该始终进行新的集合(从x复制元素).

Wrapper types

对于某些"包装"其他值的类型,构造函数可以将其参数包装在新对象中,即使它已经是请求的类型. 例如, Some(x)包装x以指示存在值(在结果可能为Somenothing的上下文中). 但是, x本身可能是对象Some(y) ,在这种情况下,结果是Some(Some(y)) ,具有两个包装级别. 另一方面, convert(Some, x)只会返回x因为它已经是Some .

Constructors that don't return instances of their own type

极少数情况下,构造函数T(x)返回非T类型的对象可能是有意义的. 如果包装器类型是其自身的逆类型(例如Flip(Flip(x)) === x ),或者在重构库时支持向后兼容的旧调用语法,则可能会发生这种情况. 但是convert(T, x)应该始终返回T类型的值.

Defining New Conversions

定义新类型时,最初应将所有创建新类型的方法都定义为构造函数. 如果很明显隐式转换将很有用,并且某些构造函数满足上述"安全"条件,则可以添加convert方法. 这些方法通常非常简单,因为它们只需要调用适当的构造函数即可. 这样的定义可能看起来像这样:

convert(::Type{MyType}, x) = MyType(x)

此方法的第一个参数的类型单例类型 Type{MyType} ,其唯一实例是MyType . 因此,仅当第一个参数是类型值MyType时才调用此方法. 注意第一个参数的语法:参数名称在::符号之前被省略,仅给出类型. 这是Julia中函数名称的语法,该参数的类型已指定,但其值不需要按名称引用. 在此示例中,由于类型是单例,因此我们已经知道它的值而无需引用参数名称.

默认情况下,某些抽象类型的所有实例都被认为与Julia Base中提供的通用convert定义"足够相似". 例如,此定义声明通过调用1参数构造函数convert任何Number类型convert为其他类型是有效的:

convert(::Type{T}, x::Number) where {T<:Number} = T(x)

这意味着新的Number类型仅需要定义构造函数,因为此定义将为它们处理convert . 还提供标识转换以处理参数已为请求的类型的情况:

convert(::Type{T}, x::T) where {T<:Number} = x

Similar definitions exist for AbstractString, AbstractArray, and AbstractDict.

Promotion

升级是指将混合类型的值转换为单个普通类型. 尽管不是严格必须的,但是通常暗示将值转换为的通用类型可以忠实地表示所有原始值. 从这个意义上说,术语"促销"是适当的,因为这些值被转换为"更大"的类型,即可以代表一个通用类型的所有输入值的类型. 但是,重要的是不要将其与面向对象(结构)的超级类型或Julia的抽象超类型的概念相混淆:提升与类型层次结构无关,而与在备用表示形式之间进行转换有关. 例如,尽管每个Int32值也可以表示为Float64值,但Int32不是Float64的子类型.

在Julia中,通过promote函数执行提升为通用"更大"类型的操作,该函数接受任意数量的参数,并返回相同数量的值的元组,转换为通用类型,如果无法提升则抛出异常. 提升的最常见用例是将数字参数转换为常见类型:

julia> promote(1, 2.5)
(1.0, 2.5)

julia> promote(1, 2.5, 3)
(1.0, 2.5, 3.0)

julia> promote(2, 3//4)
(2//1, 3//4)

julia> promote(1, 2.5, 3, 3//4)
(1.0, 2.5, 3.0, 0.75)

julia> promote(1.5, im)
(1.5 + 0.0im, 0.0 + 1.0im)

julia> promote(1 + 2im, 3//4)
(1//1 + 2//1*im, 3//4 + 0//1*im)

浮点值被提升为最大的浮点参数类型. 整数值被提升为本地机器字大小或最大整数参数类型中的较大者. 整数和浮点值的混合被提升为足以容纳所有值的浮点类型. 混合有理的整数被提升为有理. 混有浮点数的有理数被提升为浮点数. 将复杂值与实际值混合会提升为适当类型的复杂值.

这实际上是使用促销的全部内容. 剩下的只是一个聪明的应用程序,最典型的"聪明"应用程序是数字运算(如算术运算符+-*/的所有方法的定义. 这是promotion.jl给出的一些通用方法定义:

+(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)...)

These method definitions say that in the absence of more specific rules for adding, subtracting, multiplying and dividing pairs of numeric values, promote the values to a common type and then try again. That's all there is to it: nowhere else does one ever need to worry about promotion to a common numeric type for arithmetic operations – it just happens automatically. There are definitions of catch-all promotion methods for a number of other arithmetic and mathematical functions in promotion.jl, but beyond that, there are hardly any calls to promote required in Julia Base. The most common usages of promote occur in outer constructors methods, provided for convenience, to allow constructor calls with mixed types to delegate to an inner type with fields promoted to an appropriate common type. For example, recall that rational.jl provides the following outer constructor method:

Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)

这样可以进行如下调用:

julia> Rational(Int8(15),Int32(-5))
-3//1

julia> typeof(ans)
Rational{Int32}

对于大多数用户定义的类型,更好的做法是要求程序员为构造函数显式提供期望的类型,但是有时,尤其是对于数字问题,自动进行提升会很方便.

Defining Promotion Rules

尽管原则上可以直接定义promote函数的方法,但这将需要对参数类型的所有可能排列进行许多冗余的定义. 取而代之的是, promote的行为是通过一种名为promote_rule的辅助函数定义的,该函数可以为其提供方法. promote_rule函数采用一对类型对象,然后返回另一个类型对象,这样参数类型的实例将被提升为返回的类型. 因此,通过定义规则:

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

有人宣称,将64位和32位浮点值一起提升时,应将它们提升为64位浮点. 提升类型不必是参数类型之一. 以下促销规则均出现在Julia Base中:

promote_rule(::Type{BigInt}, ::Type{Float64}) = BigFloat
promote_rule(::Type{BigInt}, ::Type{Int8}) = BigInt

在后一种情况下,结果类型为BigInt因为BigInt是唯一足以容纳用于任意精度整数算术的整数的类型. 另请注意,您无需同时定义promote_rule(::Type{A}, ::Type{B})promote_rule(::Type{B}, ::Type{A}) –对称性暗示在升级过程中使用promote_rule方式.

promote_rule函数用作定义第二个函数promote_type ,该函数在给定任何数量的类型对象的情况下,将这些值作为要promote参数返回的公共类型. 因此,如果要在没有实际值的情况下知道某些类型的值的集合将提升为哪种类型,可以使用promote_type

julia> promote_type(Int8, Int64)
Int64

在内部, promote_typepromote_type内部使用,以确定应将哪种类型的promote_type值转换为promote . 但是,它本身可以很有用. 好奇的读者可以阅读promotion.jl的代码,该代码在大约35行中定义了完整的提升机制.

Case Study: Rational Promotions

最后,我们完成了正在进行的Julia有理数类型的案例研究,该案例对晋升机制具有以下晋升规则进行了相对复杂的使用:

promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:Integer} = Rational{promote_type(T,S)}
promote_rule(::Type{Rational{T}}, ::Type{Rational{S}}) where {T<:Integer,S<:Integer} = Rational{promote_type(T,S)}
promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:AbstractFloat} = promote_type(T,S)

第一条规则说,用任何其他整数类型提升有理数会提升为分子/分母类型是其分子/分母类型也提升了其他整数类型的结果的有理类型. 第二条规则将相同的逻辑应用于两种不同类型的有理数,从而导致它们各自的分子/分母类型的有理化. 第三条也是最后一条规则规定,使用浮点数推广有理数与使用浮点数推广分子/分母类型的结果相同.

少量的升级规则,再加上类型的构造函数和默认的数字convert方法,足以使有理数与Julia的所有其他数字类型(整数,浮点数和复数)完全自然地互操作. 通过以相同的方式提供适当的转换方法和升级规则,任何用户定义的数字类型都可以自然地与Julia的预定义数字进行互操作.

by  ICOPY.SITE