一个多重派发的 demo

有时,我们会知道某个变量的类型是某些类型的变量经过加减乘除运算后得到的类型,并且想要标注出来,这可以用生成函数实现,下面给出一个基于多重派发的实现。

记待实现函数为 f,其接受一系列 Type 类型的参数,并返回这些类型的变量经过加减乘除后的得到的变量的类型。

首先,f 在双参数时满足交换律,并且可以递归定义:

f(x::Type, y::Type) = f(y, x)
f(x::Type, y::Type, z::Type...) = f(x, f(y, z...))

于是,我们只需要考虑单参数以及双参数情形。对于复数,我们有:

f(::Type{Complex{T}}) where T = Complex{f(T)}
f(::Type{Complex{T}}, ::Type{Complex{S}}) where {T, S} = Complex{f(T, S)}
f(::Type{Complex{T}}, ::Type{S}) where {T, S <: Real} = Complex{f(T, S)}

于是,复数情形便转化为实数情形。我们知道整数对除法不封闭,于是可以先对其进行处理:

f(x::Type{<:Integer}, y::Type) = f(f(x), y)

现在,需要处理的情形有:不包含整数的双参数实数,单参数实数,先来处理前者:

f(::Type{BigFloat}, ::Union{Type{BigFloat}, Type{Float64}, Type{Float32}, Type{Float16}}) = BigFloat
f(::Type{Float64}, ::Union{Type{Float64}, Type{Float32}, Type{Float16}}) = Float64
f(::Type{Float32}, ::Union{Type{Float32}, Type{Float16}}) = Float32
f(::Type{Float16}, ::Type{Float16}) = Float16

单参数实数情形:

f(x::Union{Type{BigFloat}, Type{Float64}, Type{Float32}, Type{Float16}}) = x
f(::Type{BigInt}) = BigFloat
f(::Base.BitIntegerType) = Float64

最后,来处理 Missing 类型:

f(::Type{Missing}, ::Type...) = Missing

可以看到,由于 Julia 多重派发的强大威力,所生成的代码会直接返回计算结果:

julia> @code_typed f(Int, Float64)
CodeInfo(
1 ─     return Float64
) => Type{Float64}

在设计方法时推荐先分析出一般的规律,利用得到的规律一步步推出那些具体的实现是必要的,再实现之。一般来说,递归定义的数学函数,比如此例的 f,在实现时所必须要实现的情形就单参数以及双参数情形,参数可交换也会使得所需代码量缩减一半。

2 个赞