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
可让许多内建方法和可迭代对象一起玩耍,如in
、mean
、std
这等来自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
看起来越来越像向量。
可以官方地定义Squares
为AbstractArray
子类型,而不是自行定义所有行为。
要落实的方法 | 简要说明 |
---|---|
size(A) |
返回包含A 个维度尺寸的元组。 |
getindex(A, i::Int) |
若IndexLinear 则线性标量索引。 |
getindex(A, I:Vararg{Int, N}) |
若IndexCartesian 且N = ndims(A) 则N维标量索引。 |
setindex!(A, v, i::Int) |
若IndexLinear 则标量索引赋值。 |
setindex!(A, v, I::Vararg{Int, N}) |
若IndexCartesian 且N = ndims(A) 则N维标量索引赋值。 |
可选方法 | 默认定义 | 简要说明 |
---|---|---|
IndexStyle(::Type) |
IndexCartesian() |
返回IndexLinear 或IndexCartesian() ,详见下面描述。 |
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
数组,因此必须手动定义数组维度的getindex
和setindex!
。
不同于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
定义similar
、getindex
和setindex!
使其可以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
。
也应特化similar
让dims
参数(普通的Dims
尺寸元组)能接受AbstractUnitRange
对象,获取自设计的区间类型Ind
也在其中。
更多信息,查看【客户定制索引的数组】。
要落实的方法 | 简要说明 |
---|---|
strides(A) |
以元组返回内存中(元素的个数)每个维度中邻近元素的距离。如果A 是AbstractArray{T, 0} ,则返回空元组。 |
Base.unsafe_convert(::Type{Ptr{T}}, A) |
返回数组本地地址。 |
可选方法 | 默认定义 | 简要说明 |
---|---|---|
stride(A, i::Int) |
strides(A)[i] |
返回内存中(元素的个数)邻近元素k维度的距离。 |
一个步幅的数组是AbstractArray
的子类型,条目保存在内存中固定的步幅。
提供的数组的元素类型和BLAS的一致,步幅数组可利用BLAS和LAPACK协程实现更高效的线性代数协程。
一个典型的用户自定义的步幅数组的例子是以额外结构包括标准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}) |
覆盖延迟广播轴的计算。 |
广播被显式调用broadcast
或broadcast!
触发,或者被如A .+
、f .(x, y)
等“点操作”隐式触发。
具有axes
并支持索引的任何对象可以参数参与广播,默认的结果保存在Array
。
这个基本框架可通过三种主要方式扩展:
- 确保所有参数支持广播
- 为给定的参数集合选择合适的输出数组
- 为给定的参数集合选择高效的实现
不是所有类型都支持axes
和索引,但是许多类型都可便捷地允许用于广播中。
Base.broadcastable
函数在广播时被每个参数调用,允许返回支持axes
和索引不同的某些东西。
默认地,这是所有AbstractArray
和Numbers
的特性函数,这两种类型已然支持axes
和索引。
为了一小撮别的类型(包括但不限于类型本身、函数、特殊单例(missing
、nothing
)和时间),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
是广播操作(可能是融合的)的延迟表达,是一个被广播的对象。
为了这些目的,最重要的包装字段是f
和args
,分别描述函数和参数列表。
注意参数列边可以且通常包含别的嵌套的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
对象的实现。
内建的后手broadcast
和broadcast!
方法类似地构建操作的临时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))
就地广播可通过定义合适的copyto!(dest, bc::Broadcasted)
方法来支持。
因为可能想要在dest
或bc
的指定子类型上特化,为避免包之间的歧义,推荐下述惯例。
如果希望在特定风格DestStyle
上特化,为copyto!
定义下述方法:
copyto!(dest, bc::Broadcasted{DestStyle})
如果想要不借助在DestStyle
上特化来在DestType
上特化,则需要定义具备下述签名的方法:
copyto!(dest::DestType, bc::Broadcasted{Nothing})
这利用了一个copyto!
的后手实现,转换包装为Broadcasted{Nothing}
。
因此,在DestType
上特化的优先级比在DestStyle
上特化的优先级低。
为了实现如copy
和copyto!
方法,当然,必须和Broadcasted
包装一起来计算每个元素。
由两种主要方法:
Broadcast.flatten
重新计算潜在的嵌套操作为单个函数和参数平铺列表。程序员的职责是实现广播形状规则,但在有限情况下是有益的。- 遍历
axes(::Broadcasted)
的CartesianIndices
并用之索引作为结果的CartesianIndex
对象来计算结果。
优先的规则被双目BroadcastStyle
调用定义:
Base.BroadcastStyle(::StyleX, ::StyleY) = StyleXY()
这里的StyleXY
是想要选为包括StyleX
和StyleY
参数的输出的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
。
- 着实骚气了。