Skip to content

Latest commit

 

History

History
598 lines (506 loc) · 26.4 KB

接口.md

File metadata and controls

598 lines (506 loc) · 26.4 KB

Julia的许多强大且可扩展的特性来自一组日常使用的接口。 通过给某定制类型扩展些许指定方法,该类型的对象不仅接收那些功能(函数),也用于基于那些行为一般构建的其余方法。

迭代

必要方法 简要描述
iterate(iter) 返回首项的元组和初始状态(或nothing若为空)。
iterate(iter, state 返回后项和后状态(或nothing若无更多项)。
重要可选方法 默认定义 简要表述
必是IteratorSize(IterType HasLength HasLength()HasShape{N}()IsInfinite()SizeUnknown()其一,视情况而定(as appropriate)。
IteratorEltype(IterType) HasEltype EltypeUnknown()、或HasEltype(),视情况而定。
eltype(IterType) Any iterate返回的元组的第一个条目的类型。
length(iter) 未定义 若已知,则为元素个数。
size(iter, [dim...]) 未定义 若已知,则为每个维度种元素的个数。
单表IteratorSize(IterType)返回值 必要方法
HasLength() length(iter)
HasShape{N}() length(iter)size(iter, [dim...])
IsInfinite()
SizeUnknown()
单表IteratorEltype(IterType)返回值 必要方法
HasEltype() eltype(IterType)
EltypeUnkown()

连续迭代由iterate函数实现。 Julia迭代在对象外部保持迭代状态,而不是改变所遍历的对象。 迭代返回值总要么是元组和状态、要么是nothing如果没有剩余元素。 状态对象会在下一次迭代回传给迭代函数,通常认为是可迭代对象私有的实现细节。

任何定义了iterate函数的对象都是可迭代的,并且用在用于【许多依赖迭代的函数】中。 自下述语法之后,还可直接用在for循环:

for i in iter
    # 循环体
end

翻译为:

next = iterate(iter)
while next != nothing
    (i, state) = next
    # 循环体
    next = iterate(iter, state)
end

一个简单的例子是定义了长度的平方数可迭代序列:

julia> struct Squares
         count::Int
       end

julia> Base.iterate(S::Squares, state=1) = state > S.count ? nothing : (state*state, state+1)

只需要带上iterate定义,Squares类型即已变得优雅强大。 可以如下遍历所有元素:

julia> for i in Squares(7)
         println(i)
       end
1
4
9
16
25
36
49

可让许多内建方法和可迭代对象一起玩耍,如inmeanstd这等来自Statistics标准库模块的:

julia> 25 in Squares(9)
true

julia> using Statistics

julia> mean(Squares(99))
3316.6666666666665

julia> std(Squares(99))
2964.596937190619

可以再多扩展一些方法给Julia更多有关该迭代集合的信息。 大家知道Squares序列中的元素将总是Int。 通过扩展eltype方法,可提供信息给Julia助其再更复杂的方法中创造更合适的代码。 大家还知道序列中元素的个数,也可以扩展length方法:

julia> Base.eltype(::Type{Squares}) = Int

julia> Base.length(S::Squares) = S.count

现在,当要求Julia收集(collect)所有元素到一个数组,Julia可预分配正确尺寸的Vector{Int},而不是盲目地添加(push!)每个元素到Vector{Any}中。

julia> collect(Squares(7))
7-element Array{Int64,1}:
  1
  4
  9
 16
 25
 36
 49

当能依赖一般实现的时候,知道存在更简单的算法,也可以扩展指定方法。 例如,有个计算序列和的公式,即可重写更具性能的普通迭代版本方法:

julia> Base.sum(S::Squares) = (n = S.count; return n*(n+1)*(2n+1)÷6)

julia> sum(Squares(1314))
757112565

julia> sum(Squares(9527))
288280732180

贯穿Julia.Base这是很常见的模式:必要方法的一小组定义日常使用接口赋能许多奇特的行为。 在某些情况下,类型想要添加额外地特化那些非常行为,当已知在其特定情况下有更高效的算法可用。

允许用Iterators.reverse(iterator)逆序遍历集合也有用。 实际上支持逆序迭代,无论怎样,迭代类型T需要为Iterators.Reverse{T}实现iterate函数。 给定r::Iterators.Reverse{T},类型T从属的迭代器是r.itr。 在上述Squares例子中,可实现Iterators.Reverse{Squares}方法:

julia> Base.iterate(rS::Iterators.Reverse{Squares}, state=rS.itr.count) = state < 1 ? nothing : (state*state, state-1)

julia> collect(Iterators.reverse(Squares(7)))
7-element Array{Int64,1}:
 49
 36
 25
 16
  9
  4
  1

索引

要落实的方法 简要说明
getindex(X, i) X[i],索引访问元素。
setindex!(X, v, i) X[i] = v,索引赋值元素。
firstindex(X) 首个元素。
lastindex(X) 末尾元素,用于X[end]

对于上述Squares可迭代类型,可以很容易地计算第i个元素的平方数。 可以暴露该功能为S[i]表达式。 通向该行为,Squares仅需要定义getindex

julia> function Base.getindex(S::Squares, i::Int)
         1 <= i <= S.count || throw(BoundsError(S, i))
         return i*i
       end

julia> Squares(99)[55]
3025

此外,支持S[end]语法,必须定义lastindex来制定最后一个有效的索引。 还推荐定义firstindex指定第一个有效的索引:

julia> Base.firstindex(S::Squares) = 1

julia> Base.lastindex(S::Squares) = length(S)

julia> Squares(99)[end]
9801

注意,尽管上述只定义带一个整数索引的getindex。 非Int的任何索引将爆出MethodError喊冤没有匹配的方法。 为了支持区间或Int向量索引,还得定义别的方法:

julia> Base.getindex(S::Squares, i::Number) = S[convert(Int, i)]

julia> Base.getindex(S::Squares, I) = [S[i] for i in I]

julia> Squares(9)[[3,4.,5]]
3-element Array{Int64,1}:
  9
 16
 25

julia> typeof(4.)
Float64

虽然开始支持更多【某些内建类型支持的索引操作】,仍然确实一批行为。 随着添加行为,序列Squares看起来越来越像向量。 可以官方地定义SquaresAbstractArray子类型,而不是自行定义所有行为。

抽象数组

要落实的方法 简要说明
size(A) 返回包含A个维度尺寸的元组。
getindex(A, i::Int) IndexLinear则线性标量索引。
getindex(A, I:Vararg{Int, N}) IndexCartesianN = ndims(A)则N维标量索引。
setindex!(A, v, i::Int) IndexLinear则标量索引赋值。
setindex!(A, v, I::Vararg{Int, N}) IndexCartesianN = ndims(A)则N维标量索引赋值。
可选方法 默认定义 简要说明
IndexStyle(::Type) IndexCartesian() 返回IndexLinearIndexCartesian(),详见下面描述。
getindex(A, I...) 按照标量getindex定义 多维且非标量索引
setindex!(A, I...) 按照标量setindex!定义 多维且非标量索引赋值
iterate 按照标量getindex定义 迭代。
length(A) prod(size(A)) 元素个数。
similar(A) similar(A, eltype(A), size(A)) 返回拥有相同形状和元素类型的可变数组。
similar(A, ::Type{S}) similar(A, S, size(A)) 返回相同形状和指定元素类型的可变数组。
similar(A, dims::NTuple{Int}) similar(A, eltype(A), dims) 返回相同元素类型和维度尺寸的可变数组。
similar(A, ::Type{S}, dims::NTuple{Int}) Array{S}(undef, dims) 返回指定元素类型和维度尺寸的可变数组。
非传统索引 默认定义 简要说明
axes(A) map(OneTo, size(A)) 返回有效索引的AbstractUnitRange
Base.similar(A, ::Type{S}, inds::NTuple{Ind}) similar(A, S, Base.to_shape(inds)) 返回指定索引元组的可变数组(见下述)。
Base.similar(T::Union{Type, Function}, inds) T(Base.to_shape(inds)) 返回指定索引元组的、与T近似的数组(见下述)。

如果一个类型定义为AbstractArray的子类型,继承非常丰富的行为,包括构建在单个元素访问上的迭代和多维索引。 查看【多维数组】和【数组】获知更多支持的方法。

定义AbstractArray的一个关键部分是IndexStyle。 因为索引对数组来说如此重要,并且经常在热循环中发生,尽可能让索引和索引复制高效是重要的。 数组数据结构典型以两种方式定义了一个:无论只是通过一个索引(线性索引)还是用每个维度指定的索引高效地访问元素。 这两种形态被Julia指定为IndexLinear()IndexCartesian()。 转换线性索引为多维索引下表令人发指地非常昂贵(高成本),因此提供基于特质(trait-based)机制使得对于所有数组类型都能产生高效的一般代码。

这个决定类型标量索引方法的区别必须定义。 IndexLinear()数组简单:只需定义getindex(A::ArrayType, i::Int)。 当数组后续地被多维索引集合索引,退路getindex(A::AbstractArray, I...)()高效地转换索引为线性索引,并且接着调用上述方法。 IndexCartesian()数组,另一方面,要求为每个以ndim(A)Int索引支持的维度定义方法。 举个例子,来自SparseArrays标准库模块的SparseMatrixCSC仅定义两个维度,因此只定义getindex(A::SparseMatrixCSC, i::Int, j::Int)setindex!同理(the same holds for)。

回到上述Squares序列,可以将之定义为AbstractArray{Int, 1}的子类型:

julia> struct SquaresVector <: AbstractArray{Int, 1}
         count::Int
       end

julia> Base.size(S::SquaresVector) = (S.count, )

julia> Base.IndexStyle(::Type{<:SquaresVector}) = IndexLinear()

julia> Base.getindex(S::SquaresVector, i::Int) = i*i

注意指定AbstractArray的两个参数非常重要;第一个参数定义eltype,第二个参数定义ndims。 超类型和这三个方法是让SquaresVector成为可迭代、可索引、拥有完整功能的数组的全部:

julia> s = SquaresVector(5)
5-element SquaresVector:
  1
  4
  9
 16
 25

julia> s[s .> 9]
2-element Array{Int64,1}:
 16
 25

julia> s + s
5-element Array{Int64,1}:
  2
  8
 18
 32
 50

julia> sin.(s)
5-element Array{Float64,1}:
  0.8414709848078965
 -0.7568024953079282
  0.4121184852417566
 -0.2879033166650653
 -0.13235175009777303

如更复杂的例子,让咱一起构建基于Dict的N维类稀疏数组类型:

julia> struct SparseArray{T, N} <: AbstractArray{T, N}
         data::Dict{NTuple{N, Int}, T}
         dims::NTuple{N, Int}
       end

julia> SparseArray(::Type{T}, dims::Int...) where {T} = SparseArray(T, dims)
SparseArray

julia> SparseArray(::Type{T}, dims::NTuple{N, Int}) where {T, N} = SparseArray{T, N}(Dict{NTuple{N, Int}, T}(), dims)
SparseArray

julia> Base.size(A::SparseArray) = A.dims

julia> Base.similar(A::SparseArray, ::Type{T}, dims::Dims) where {T} = SparseArray(T, dims)

julia> Base.getindex(A::SparseArray{T, N}, I::Vararg{Int, N}) where {T, N} = get(A.data, I, zero(T))

julia> Base.setindex!(A::SparseArray{T, N}, v, I::Vararg{Int, N}) where {T, N} = (A.data[I] = v)

注意这是一个IndexCartesian数组,因此必须手动定义数组维度的getindexsetindex!。 不同于SquaresVector,可以定义setindex!,则可修改数组:

julia> A = SparseArray(Float64, 3, 3)
3×3 SparseArray{Float64,2}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

julia> fill!(A, 2)
3×3 SparseArray{Float64,2}:
 2.0  2.0  2.0
 2.0  2.0  2.0
 2.0  2.0  2.0

julia> A[:] = 1:length(A)
1:9

julia> A
3×3 SparseArray{Float64,2}:
 1.0  4.0  7.0
 2.0  5.0  8.0
 3.0  6.0  9.0

索引AbstractArray的结果自身是数组(例如通过AbstractRange索引)。 而AbstractArray后手方法用similar分配合适尺寸和元素类型的Array实例,即上述通过基本索引方法填充的,当实现一个数组包装时,经常想要也返回包装的结果:

julia> A[1:2, :]
2×3 SparseArray{Float64,2}:
 1.0  4.0  7.0
 2.0  5.0  8.0

在本例中,通过定义Base.similar{T}(A::SparseArray, ::Type{T}, dims::Dims)创建适当的包装数组。 注意,因为similar支持一个、两个参数形式,在大多数情况下,只需要特化三个参数的形式。 为使工作SparseArray是可变的(支持setindex!)。 还要给SparseArray定义similargetindexsetindex!使其可以copy数组:

julia> copy(A)
3×3 SparseArray{Float64,2}:
 1.0  4.0  7.0
 2.0  5.0  8.0
 3.0  6.0  9.0

除了上边所有可迭代和可索引的方法,这些类型也可以互动,可使用Julia.Base中为AbstractArray及其子类型定义的大部分方法:

julia> A[SquaresVector(3)]
3-element SparseArray{Float64,1}:
 1.0
 4.0
 9.0

julia> sum(A)
45.0

如果定义数组类型允许非传统索引(脚标不是1开始的某物),需要特化axes。 也应特化similardims参数(普通的Dims尺寸元组)能接受AbstractUnitRange对象,获取自设计的区间类型Ind也在其中。 更多信息,查看【客户定制索引的数组】。

步幅(strided)数组

要落实的方法 简要说明
strides(A) 以元组返回内存中(元素的个数)每个维度中邻近元素的距离。如果AAbstractArray{T, 0},则返回空元组。
Base.unsafe_convert(::Type{Ptr{T}}, A) 返回数组本地地址。
可选方法 默认定义 简要说明
stride(A, i::Int) strides(A)[i] 返回内存中(元素的个数)邻近元素k维度的距离。

一个步幅的数组是AbstractArray的子类型,条目保存在内存中固定的步幅。 提供的数组的元素类型和BLAS的一致,步幅数组可利用BLASLAPACK协程实现更高效的线性代数协程。 一个典型的用户自定义的步幅数组的例子是以额外结构包括标准Array

警告:不要实现这些方法,如果底层存储事实上并非步幅的,否则会导致错误结果或端故障。

这儿给出一些步幅数组和非步幅数组类型的演示:

1:5 # 非步幅(无与此数组关联的存储)
Vector(1:5) # 以步幅(1, )跨过
A = [1 5; 2 6; 3 7; 4 8] # 以步幅(1, 4)跨过
V = view(A, 1:2, :) # 以步幅(1, 4)跨过
V = view(A, 1:2:3, 1:2) # 以步幅(2, 4)跨过
V = view(A, [1,2,4], :) # 非步幅(因为行之间的空间不固定)

定制广播

要落实的方法 简要说明
Base.BroadcastStyle(::Type{SrcType}) = SrcStyle() SrcType的广播行为。
Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType}) 输出容器的配置。
可选方法 简要说明
Base.BroadcastStyle(::StyleX, ::StyleY) = StyleXY() 混合风格的优先规则。
Base.broadcast_axes(x) 用于广播目的之x的索引的声明。
Base.broadcastable(x) 转换x为有axes并支持索引的对象。
绕开默认机械(覆盖) 简要说明
Base.copy(bc::Broadcasted{DestStyle}) 自定义的broadcast实现。
Base.copyto!(dest, bc::Broadcasted{DestStyle}) 自定义的broadcast!实现,在DestStyle的特化。
Base.copyto!(dest::DestType, bc::Broadcasted{Nothing}) 自定义的broadcast!实现,在DestType的特化。
Base.Broadcast.broadcasted(f, args...) 覆盖融合表达式中默认的延迟行为。
Base.Broadcast.instantiate(bc::Broadcasted{DestStyle}) 覆盖延迟广播轴的计算。

广播被显式调用broadcastbroadcast!触发,或者被如A .+f .(x, y)等“点操作”隐式触发。 具有axes并支持索引的任何对象可以参数参与广播,默认的结果保存在Array。 这个基本框架可通过三种主要方式扩展:

  • 确保所有参数支持广播
  • 为给定的参数集合选择合适的输出数组
  • 为给定的参数集合选择高效的实现

不是所有类型都支持axes和索引,但是许多类型都可便捷地允许用于广播中。 Base.broadcastable函数在广播时被每个参数调用,允许返回支持axes和索引不同的某些东西。 默认地,这是所有AbstractArrayNumbers的特性函数,这两种类型已然支持axes和索引。 为了一小撮别的类型(包括但不限于类型本身、函数、特殊单例(missingnothing)和时间),Base.broadcastable返回包装在Ref的参数,作为零维标量用于广播目的。 定制类型可相似地特化Base.broadcastable来定义形状,但应当遵守collect(Base.broadcastable(x)) == collect(x)的惯例。 一个值得注意的例外是AbstractString;字符串是为广播目的的、行为如标量的特殊场景,即使可按字符迭代(查看【Julia抽象语法树】获取更多信息)。

接下来的两步(选择输出数组和实现)取决于为给定的参数集合确定一个答案。 广播必须考虑参数所有类型变化并浓缩(collapse)为一个输出数组和一个实现。 广播称此唯一答案为“风格”。 每个可广播对象各自都有优先的风格,一个类似提升的系统用来结合这些风格到单个答案,即“目标风格”。

广播风格

Base.BroadcastStyle是抽象类型,所有广播风格均自此派生。 当用作函数,有两种形式,单目和双目。 单目变种规定倾向实现指定广播行为和(或)输出类型,并不希望依赖默认后手Broadcast.DefaultArrayStyle

覆盖这些默认玩意儿,可以为对象自定义BroadcastStyle

struct MyStyle <: Broadcast.BroadcastStyle end
Base.BroadcastStyle(::Type{<:MyType}) = MyStyle()

在某些场景,不用定义MyStyle是方便的,这种情况下,可权衡(leverage)以下通用广播包装:

  • Base.BroadcastStyle(::Type{<:MyType}) = Broadcast.Style{MyType}()可用于任意类型。
  • Base.BroadcastStyle(::Type{<:MyType}) = Broadcast.ArrayStyle{MyType}(),如果MyType是某种AbstractArray则优先。
  • 针对仅支持特定维度的AbstractArray子类型,创建Broadcast.AbstractArrayStyle{N}的子类型(见下述)。

当广播操作包含若干参数,各个参数风格结合来决定单个DestStyle控制输出容器的类型。更多细节,见下述。

选择合适的输出数组

为每个广播操作计算广播风格允许重载和特化。 事实上结果数组的配置由similar处理,将被广播的对象作为首个参数:

Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType})

后手定义是:

similar(bc::Broadcasted{DefaultArrayStyle{N}}, ::Type{ElType}) where {N,ElType} = similar(Array{ElType}, axes(bc))

然而,如果需要,可以在任何或全部这些参数上特化。 最终参数bc是广播操作(可能是融合的)的延迟表达,是一个被广播的对象。 为了这些目的,最重要的包装字段是fargs,分别描述函数和参数列表。 注意参数列边可以且通常包含别的嵌套的Broadcasted包装。

为一个完整样例,创建一个类型ArrayAndChar保存数组和单个字符:

struct ArrayAndChar{T,N} <: AbstractArray{T,N}
    data::Array{T,N}
    char::Char
end
Base.size(A::ArrayAndChar) = size(A.data)
Base.getindex(A::ArrayAndChar{T,N}, inds::Vararg{Int,N}) where {T,N} = A.data[inds...]
Base.setindex!(A::ArrayAndChar{T,N}, val, inds::Vararg{Int,N}) where {T,N} = A.data[inds...] = val
Base.showarg(io::IO, A::ArrayAndChar, toplevel) = print(io, typeof(A), " with char '", A.char, "'")

或许想要广播维持char类型的“元数据”。首先定义:

Base.BroadcastStyle(::Type{<:ArrayAndChar}) = Broadcast.ArrayStyle{ArrayAndChar}()

这表示还必须定义对应的similar方法:

function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{ArrayAndChar}}, ::Type{ElType}) where ElType
    # 扫描`ArrayAndChar`的输入:
    A = find_aac(bc)
    # 用`A`的`char`字段创建输出:
    ArrayAndChar(similar(Array{ElType}, axes(bc)), A.char)
end

# `A = find_aac(As)`返回参数中第一个`ArrayAndChar`:
find_aac(bc::Base.Broadcast.Broadcasted) = find_aac(bc.args)
find_aac(args::Tuple) = find_aac(find_aac(args[1]), Base.tail(args))
find_aac(x) = x
find_aac(a::ArrayAndChar, rest) = a
find_aac(::Any, rest) = find_aac(rest)

从上述定义中得到下面行为:

a = ArrayAndChar([1 2; 3 4], 'x')
println(a)
println(a .+ 1)
println(a .+ [5,10])

用定制实现扩展广播

一般来说,广播操作由保持参数应用到函数的Broadcasted容器表达。 这些参数自身可能嵌套更多Broadcasted容器,形成要被计算的大的表达式树。 一个Broadcasted容器的嵌套树直接由隐式的“点”语法构建,举个例子,5 .+ 2.*x临时表达Broadcasted(+, 5, Broadcasted(*, 2, x))。 这对用户是透明的,很快意识到调用copy,但这是容器提供给定制类型的用户广播扩展的基础。 内建的广播装置接着根据参数确定结果类型和大小,分配空间,最后通过默认的copyto!(::AbstractArray, ::Broadcasted)方法拷贝Broadcasted对象的实现。 内建的后手broadcastbroadcast!方法类似地构建操作的临时Broadcasted表达,因此走相同的代码路径。 着允许定制数组实现提供自有的copyto!特化给自定义且优化的广播。 这再次由计算所得的广播风格决定。 保存Broadcasted类型的第一个类型参数是如此重要的操作部分,允许重载和特化。

对于某些类型,贯穿广播嵌套层级的融合操作的装置不可用,或者不是效率不是线性增长的(be done more efficiently incrementally)。 在这些情况下,需要或者想要如broadcast(*, x, broadcast(+, x, 1))计算x .* (x .+ 1),此处内部操作在外部操作之前被计算。 这些稍微急切的操作直接受到一些间接的支持;不是直接构建Broadcasted对象,Julia减弱融合表达式x .* (x .+ 1)Broadcast.broadcasted(*, x, Broadcasted(+, x, 1))。 现在,默认地broadcasted只调用Broadcasted构造方法来创建融合表达式树的延迟表达,但是用户可以选择为特定的函数和参数组合覆盖该默认逻辑。

作为一个例子,内建AbstractRange对象使用这个装置来优化被广播的表达式片段,可被急切地纯粹按照开始、步长和长度(结束)被计算,而不用计算每个元素。 正如别的任何装置,broadcasted也计算并暴露自身参数地组合广播风格,因此,不用在broadcasted(f, args...)上特化,可以为任何风格、函数、参数的组合特化broadcasted(::DestStyle, f, args...)

举个例子,下面的定义支持复数区间:

broadcasted(::DefaultArrayStyle{1}, ::typeof(-), r::OrdinalRange) = range(-first(r), step=-step(r), length=length(r))

扩展就地(in-place)广播

就地广播可通过定义合适的copyto!(dest, bc::Broadcasted)方法来支持。 因为可能想要在destbc的指定子类型上特化,为避免包之间的歧义,推荐下述惯例。

如果希望在特定风格DestStyle上特化,为copyto!定义下述方法:

copyto!(dest, bc::Broadcasted{DestStyle})

如果想要不借助在DestStyle上特化来在DestType上特化,则需要定义具备下述签名的方法:

copyto!(dest::DestType, bc::Broadcasted{Nothing})

这利用了一个copyto!的后手实现,转换包装为Broadcasted{Nothing}。 因此,在DestType上特化的优先级比在DestStyle上特化的优先级低。

和被广播对象一起玩

为了实现如copycopyto!方法,当然,必须和Broadcasted包装一起来计算每个元素。 由两种主要方法:

  • Broadcast.flatten重新计算潜在的嵌套操作为单个函数和参数平铺列表。程序员的职责是实现广播形状规则,但在有限情况下是有益的。
  • 遍历axes(::Broadcasted)CartesianIndices并用之索引作为结果的CartesianIndex对象来计算结果。

编写二元广播规则

优先的规则被双目BroadcastStyle调用定义:

Base.BroadcastStyle(::StyleX, ::StyleY) = StyleXY()

这里的StyleXY是想要选为包括StyleXStyleY参数的输出的BroadcastStyle。 例如:

Base.BroadcastStyle(::Broadcast.Style{Tuple}, ::Broadcast.AbstractArrayStyle{0}) = Broadcast.Style{Tuple}()

表明Tuple争取零维数组(输出容器是元组)。 不需要(或不应该)定义该调用的全部参数顺序,是不值得的;定义一个就足够了,无论用户提供的参数顺序是什么。

对于AbstractArray类型,定义BroadcastStyle接替备胎(fallback choice),Broadcast.DefaultArrayStyle。 这个DefaultArrayStyle和抽象超类型AbstarctArrayStyle,保存维度信息为类型参数来支持特化有着固定维度要求的数组类型。

DefaultArrayStyle消费任何AbstractArrayStyle定义的别的方法,因为下述定义:

BroadcastStyle(a::AbstractArrayStyle{Any}, ::DefaultArrayStyle) = a
BroadcastStyle(a::AbstractArrayStyle{N}, ::DefaultArrayStyle{N}) where N = a
BroadcastStyle(a::AbstractArrayStyle{M}, ::DefaultArrayStyle{N}) where {M,N} = typeof(a)(_max(Val(M),Val(N)))

不需要编写二元BraodcastStyle规则,除非想要创立两个或更多非DefaultArrayStyle类型。

如果数组类型没有固定维度要求,则应继承AbstractArrayStyle。 举个例子,稀疏数组代码有下述定义:

struct SparseVecStyle <: Broadcast.AbstractArrayStyle{1} end
struct SparseMatStyle <: Broadcast.AbstractArrayStyle{2} end
Base.BroadcastStyle(::Type{<:SparseVector}) = SparseVecStyle()
Base.BroadcastStyle(::Type{<:SparseMatrixCSC}) = SparseMatStyle()

无论何时派生AbstractArrayStyle,需要定义维度组合规则,通过创建带Val(N)参数的风格构造方法完成。 例如:

SparseVecStyle(::Val{0}) = SparseVecStyle()
SparseVecStyle(::Val{1}) = SparseVecStyle()
SparseVecStyle(::Val{2}) = SparseMatStyle()
SparseVecStyle(::Val{N}) where N = Broadcast.DefaultArrayStyle{N}()

这些规则表明SparseVecStyle的零维和一维数组组合产生别的SparseVecStyle,二维数组组合产生SparseMatStyle,任何别的高维度落入愚钝的任意维度框架。 这些规则允许广播保持结果为一维或二维输出的操作的稀疏表达,但产生任何别的维度的Array


译后感

  • 着实骚气了。