AnyMath 文档

转换及推广

Julia有一个系统,用于将数学运算符的参数提升为通用类型,这在其他各个部分中已经提到,包括 整数和浮点数, 数学运算和初等函数, 类型,以及 方法。 在本节中,我们将解释此提升系统的工作原理,以及如何将其扩展到新类型并将其应用于除内置数学运算符之外的函数。 传统上,编程语言在促进算术参数方面分为两大阵营:

**内置算术类型和运算符的自动提升。*在大多数语言中,内置数字类型,当用作中缀语法的算术运算符的操作数时,例如 +, -, *,而 /,自动提升为通用类型以产生预期的结果。 C,Java,Perl和Python,仅举几例,都正确地计算总和 1 + 1.5 作为浮点值 2.5,即使其中一个操作数 + 为整数。 这些系统非常方便,而且设计得非常仔细,程序员通常几乎看不见它们:在编写这样一个表达式时,几乎没有人有意识地想到这种提升正在发生,但是编译器和解释器必须在加法之前执行转换,因为整数和浮点值不能按原样添加。 因此,此类自动转换的复杂规则不可避免地成为此类语言规范和实现的一部分。 **没有自动促销。*这个阵营包括Ada和ML-非常"严格"的静态类型语言。 在这些语言中,每个转换都必须由程序员明确指定。 因此,示例表达式 1 + 1.5 将是Ada和ML中的编译错误。 相反,必须写 实数(1)+1.5,显式转换整数 1 到执行加法之前的浮点值。 然而,到处都是显式转换是如此不方便,以至于即使Ada也有一定程度的自动转换:整数字面量被自动提升为预期的整数类型,浮点字面量也被类似地提升为适当的浮点类型。

从某种意义上说,Julia属于"没有自动提升"类别:数学运算符只是具有特殊语法的函数,函数的参数永远不会自动转换。 然而,人们可能会注意到,将数学运算应用于各种各样的混合参数类型只是多态多重调度的极端情况-Julia的调度和类型系统特别适合处理。 数学操作数的"自动"提升只是作为一个特殊的应用程序出现:Julia为数学运算符提供了预定义的catch-all调度规则,当某些操作数类型的组合不存在特定实现时调用。 这些包罗万象的规则首先使用用户可定义的提升规则将所有操作数提升为通用类型,然后为结果值(现在为相同类型)调用相关运算符的专门实现。 用户定义的类型可以通过定义与其他类型转换的方法,并提供少量的提升规则来定义与其他类型混合时应该提升到什么类型,从而轻松参与此提升系

转换

获取某种类型值的标准方法 T 是调用类型的构造函数, T(x). 但是,在某些情况下,可以方便地将值从一种类型转换为另一种类型,而无需程序员明确要求它。 一个例子是在数组中赋值:如果 A 是一个 向量{Float64},表达式 A[1]=2 应该通过自动转换工作 2Int型漂浮64,并将结果存储在数组中。 这是通过 转换/转换功能。

转换/转换 函数通常采用两个参数:第一个是类型对象,第二个是要转换为该类型的值。 返回的值是转换为给定类型实例的值。 理解此函数的最简单方法是查看它的作用:

julia> x = 12
12

julia> typeof(x)
Int64

julia> xu = convert(UInt8, x)
0x0c

julia> typeof(xu)
UInt8

julia> xf = convert(AbstractFloat, x)
12.0

julia> typeof(xf)
Float64

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

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

转换并不总是可能的,在这种情况下,a 方法;方法被抛出,表明 转换/转换 不知道如何执行请求的转换:

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

某些语言考虑将字符串解析为数字或将数字格式化为字符串以进行转换(许多动态语言甚至会自动为您执行转换)。 朱莉娅的情况并非如此。 尽管某些字符串可以被解析为数字,但大多数字符串都不是数字的有效表示,只有非常有限的子集是。 因此,在朱莉娅专用 解析,解析函数必须用于执行此操作,使其更显式。

何时是 转换/转换 打电话?

以下语言构造调用 转换/转换:

*分配给数组将转换为数组的元素类型。 *分配给对象的字段将转换为字段的声明类型。 *构造一个对象 新的转换为对象的声明字段类型。 *分配给具有声明类型的变量(例如 本地x::T)转换为该类型。 *具有声明返回类型的函数将其返回值转换为该类型。 *将值传递给 ccall将其转换为相应的参数类型。

转换与建筑

请注意, 转换(T,x) 似乎与…​…​几乎相同 T(x). 事实上,它通常是。 然而,有一个关键的语义差异:由于 转换/转换 可以隐式调用,其方法仅限于被认为是"安全"或"不令人惊讶"的情况。 转换/转换 只会在表示相同基本类型事物的类型之间进行转换(例如数字的不同表示,或不同的字符串编码)。 它通常也是无损的;将值转换为不同的类型并再次返回应该导致完全相同的值。

有四种一般情况下,构造函数不同于 转换/转换:

与其参数无关的类型的构造函数

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

可变集合

转换(T,x) 预计将返回原始 x 如果 x 已经是类型 T. 相比之下,如果 T 是一个可变的集合类型 T(x) 应该总是创建一个新的集合(从 x).

包装类型

对于一些"包装"其他值的类型,构造函数可以将其参数包装在新对象中,即使它已经是请求的类型。 例如 一些(x) 包裹 x 指示值存在(在结果可能是 一些什么都没有). 然而, x 本身可能是对象 一些(y),在这种情况下,结果是 一些(一些(y)),有两个层次的包装。 转换(Some,x) 另一方面,只会回来 x 由于它已经是一个 一些.

不返回自己类型的实例的构造函数

在_very rare_情况下,它可能对构造函数有意义 T(x) 返回非类型的对象 T. 如果包装类型是它自己的逆(例如 翻转(Flip(x))===x),或者在重构库时支持旧的调用语法以向后兼容。 但是 转换(T,x) 应始终返回类型的值 T.

定义新转换

当定义一个新类型时,最初所有创建它的方法都应该定义为构造函数。 如果很明显隐式转换将是有用的,并且某些构造函数满足上述"安全"标准,那么 转换/转换 可以添加方法。 这些方法通常非常简单,因为它们只需要调用适当的构造函数。 这样的定义可能如下所示:

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

此方法的第一个参数的类型为 类型{MyType},其中唯一的实例是 [医]类型. 因此,只有当第一个参数是类型值时才调用此方法 [医]类型. 请注意用于第一个参数的语法:参数名称在 :: 符号,并且只给出类型。 这是Julia中函数参数的语法,该参数的类型已指定,但其值不需要通过名称引用。

默认情况下,某些抽象类型的所有实例都被认为与通用类型"足够相似" 转换/转换 定义在Julia Base中提供。 例如,这个定义声明它对 转换/转换 任何 电话号码 通过调用一个参数构造函数来键入任何其他类型:

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

这意味着新的 电话号码 类型只需要定义构造函数,因为这个定义将处理 转换/转换 为了他们。 还提供了身份转换来处理参数已经是请求类型的情况:

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

类似的定义存在于 抽象字符串, 抽象阵列,和 摘要;摘要.

推广活动

提升是指将混合类型的值转换为单个通用类型。 虽然不是绝对必要的,但通常暗示值转换到的公共类型可以忠实地表示所有原始值。 从这个意义上讲,术语"提升"是合适的,因为值被转换为"更大"类型-即可以表示单个通用类型中的所有输入值的类型。 但是,重要的是不要将其与面向对象(结构)超类型或Julia的抽象超类型概念混淆:提升与类型层次结构无关,而一切都与交替表示之间的转换有关。 例如,虽然每 Int32值也可以表示为 漂浮64价值, Int32 不是 漂浮64.

提升到一个共同的"更大"类型是由Julia执行的 推广函数,它接受任意数量的参数,并返回相同数量的值的元组,转换为通用类型,或者在无法提升时抛出异常。 提升的最常见用例是将数字参数转换为通用类型:

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>推广(1.5,im)
(1.5+0.0im,0.0+1.0im)

julia>推广(1+2im,3//4)
(1//1 + 2//1&ast;我, 3//4 + 0//1&ast;im)

浮点值被提升为浮点参数类型中最大的值。 整数值被提升为最大的整数参数类型。 如果类型大小相同但符号不同,则选择无符号类型。 整数和浮点值的混合物被提升为足够大的浮点类型以容纳所有值。 与理性混合的整数被提升为理性。 与漂浮物混合的理性被提升为漂浮物。 与实数值混合的复数值被提升为适当种类的复数值。

这就是使用促销活动的全部内容。 其余的只是一个聪明的应用程序的问题,最典型的"聪明"应用程序是定义数字运算的所有方法,如算术运算符 +, -, */. 以下是在https://github.com/JuliaLang/julia/blob/master/base/promotion.jl[脧锚脧赂`晋升。jl`]:

+(x::Number, y::Number) = +(promote(x,y)...)
-(x::Number, y::Number) = -(promote(x,y)...)
&ast;(x::Number, y::Number) = &ast;(promote(x,y)...)
/(x::Number, y::Number) = /(promote(x,y)...)

这些方法定义表示,在没有更具体的数字值对的加、减、乘和除规则的情况下,将值提升为通用类型,然后重试。 这就是它的全部内容:没有其他地方需要担心提升到算术运算的通用数字类型-它只是自动发生的。 还有一些其他算术和数学函数的catch-all推广方法的定义https://github.com/JuliaLang/julia/blob/master/base/promotion.jl[脧锚脧赂`晋升。jl`],但除此之外,几乎没有任何电话 推广 茱莉亚基地需要。 最常见的用法 推广 发生在外部构造函数方法中,为了方便起见,允许具有混合类型的构造函数调用委托给内部类型,并将字段提升为适当的公共类型。 例如,回想一下https://github.com/JuliaLang/julia/blob/master/base/rational.jl[脧锚脧赂`理性。jl`]提供以下外部构造函数方法:

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

这允许像下面这样的调用工作:

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

julia> typeof(x)
Rational{Int32}

对于大多数用户定义类型,更好的做法是要求程序员显式地向构造函数提供预期的类型,但有时,特别是对于数字问题,自动执行提升可能会很方便。

定义促销规则

虽然原则上可以为 推广 直接函数,这将需要对参数类型的所有可能排列进行许多冗余定义。 相反, 推广 是用一个辅助函数来定义的 促进/促进,哪一个可以提供方法。 该 促进/促进 函数接受一对类型对象并返回另一个类型对象,这样参数类型的实例将被提升为返回的类型。 因此,通过定义规则:

import Base: 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

在后一种情况下,结果类型为 比金特比金特 是唯一一个足够大的类型来容纳任意精度整数算术的整数。 另请注意,不需要定义两者 promote_rule(::类型{A},::类型{B})promote_rule(::类型{B},::类型{A}) --对称性是由方式暗示的 促进/促进 在推广过程中使用。

促进/促进 函数用作构建块来定义第二个函数,称为 promote_type,给定任意数量的类型对象,返回这些值作为参数的公共类型 推广 应推广。 因此,如果一个人想知道,在没有实际值的情况下,某些类型的值集合将推广到什么类型,可以使用 promote_type:

julia> promote_type(Int8, Int64)
Int64

请注意,我们不*不*超载 promote_type 直接:我们超载 促进/促进 相反。 promote_type 用途 促进/促进,并增加了对称性。 直接重载它会导致歧义错误。 我们超载 促进/促进 定义应该如何促进事物,我们使用 promote_type 来查询。

内部, promote_type 用于 推广 确定应将参数值转换为何种类型以进行提升。 好奇的读者可以在https://github.com/JuliaLang/julia/blob/master/base/promotion.jl[脧锚脧赂`晋升。jl`],它在大约35行中定义了完整的提升机制。

案例研究:理性促销

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

import Base: promote_rule
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)

第一条规则说,用任何其他整数类型提升有理数会提升到有理类型,其分子/分母类型是用其他整数类型提升其分子/分母类型的结果。 第二条规则将相同的逻辑应用于两种不同类型的有理数,从而导致其各自分子/分母类型的推广的理性。 第三个也是最后一个规则规定,用浮点数促进理性会产生与用浮点数促进分子/分母类型相同的类型。

这一小部分提升规则,以及类型的构造函数和默认值 转换/转换 数字的方法,足以使有理数与Julia的所有其他数字类型-整数,浮点数和复数-完全自然地互操作。 通过以相同的方式提供适当的转换方法和提升规则,任何用户定义的数字类型都可以与Julia的预定义数字一样自然地互操作。