接口
Julia的很多功能和可扩展性来自一系列非正式接口。 通过扩展一些特定的方法来为自定义类型工作,该类型的对象不仅可以接收这些功能,而且还可以在其他方法中使用它们,这些方法通常是基于这些行为编写的。
迭代
有两种方法始终是必需的:
| 所需方法 | 简介 |
|---|---|
返回第一项和初始状态的元组或 |
|
|
返回下一个项目和下一个状态的元组或 |
在某些情况下,还有几种方法应该定义。 请注意,您应该始终定义至少一个 基地。迭代器大小(IterType) 和 长度(iter) 因为默认定义的 基地。迭代器大小(IterType) 是 基地。长().
| 方法 | 何时应该定义此方法? | 默认定义 | 简介 |
|---|---|---|---|
如果默认值不合适 |
|
其中一个 |
|
如果 |
(undefined) |
项目数量(如果已知) |
|
如果 |
(undefined) |
每个维度中的项数(如果已知) |
|
如果默认值不合适 |
|
要么 |
|
如果默认值不合适 |
|
返回的元组的第一个条目的类型 |
|
*必须*如果迭代器是有状态的定义 |
|
迭代器完成的快速路径提示。 如果未定义有状态迭代器,则检查done-ness的函数,例如 |
顺序迭代由 迭代功能。 Julia迭代器可以从对象外部跟踪迭代状态,而不是在迭代对象时改变对象。 来自iterate的返回值始终是值和状态的元组,或者 什么都没有 如果没有元素保留。 State对象将在下一次迭代时传递回迭代函数,并且通常被认为是可迭代对象私有的实现细节。
for item in iter # or "for item = iter"
# body
end
被翻译成:
next = iterate(iter)
while next !== nothing
(item, state) = next
# body
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)
只有 迭代定义, 正方形 型已经相当强大了。 我们可以遍历所有的元素:
julia> for item in Squares(7)
println(item)
end
1
4
9
16
25
36
49
julia> 25 in Squares(10)
true
julia> sum(Squares(100))
338350
我们还可以扩展一些方法来向Julia提供有关此可迭代集合的更多信息。 我们知道 正方形 序列将永远是 Int型. 通过扩展 eltype,eltype方法,我们可以将这些信息提供给Julia,并帮助它在更复杂的方法中制作更专业的代码。 我们也知道序列中的元素数量,因此我们可以扩展 长度,太:
julia> Base.eltype(::Type{Squares}) = Int # Note that this is defined for the type
julia> Base.length(S::Squares) = S.count
julia> collect(Squares(4))
4-element Vector{Int64}:
1
4
9
16
虽然我们可以依赖泛型实现,但我们也可以扩展我们知道有更简单算法的特定方法。 例如,有一个计算平方和的公式,因此我们可以用更高性能的解决方案复盖通用迭代版本:
julia> Base.sum(S::Squares) = (n = S.count; return n*(n+1)*(2n+1)÷6)
julia> sum(Squares(1803))
1955361914
这在Julia Base中是一个非常常见的模式:一小组必需的方法定义了一个非正式的接口,可以实现许多更漂亮的行为。 在某些情况下,当类型知道可以在其特定情况下使用更有效的算法时,他们会希望额外专门化这些额外的行为。
通过迭代允许对_reverse order_中的集合进行迭代通常也很有用 迭代器。反向(迭代器). 要实际支持逆序迭代,但是,迭代器类型 T 需要实施 迭代 为 迭代器。反向{T}. (给 r::迭代器。反向{T},类型的底层迭代器 T 是 r.itr.)在我们的 正方形 例如,我们将实施 迭代器。反向{Squares} 方法:
julia> Base.iterate(rS::Iterators.Reverse{Squares}, state=rS.itr.count) = state < 1 ? nothing : (state*state, state-1)
julia> collect(Iterators.reverse(Squares(4)))
4-element Vector{Int64}:
16
9
4
1
索引
| 实现方法 | 简介 |
|---|---|
|
|
|
|
|
第一个索引,用在 |
|
最后一个索引,用于 |
为 正方形 上面可迭代,我们可以很容易地计算 i序列的第三个元素通过平方. 我们可以将其公开为索引表达式 S[i]. 选择加入此行为, 正方形 只需要定义 getindex,getindex:
julia> function Base.getindex(S::Squares, i::Int)
1 <= i <= S.count || throw(BoundsError(S, i))
return i*i
end
朱莉娅>广场(100)[23]
529
此外,为了支持语法 S[开始] 和 S[完],我们必须定义 第一索引和 lastindex,lastindex分别指定第一个和最后一个有效索引:
julia> Base.firstindex(S::Squares) = 1
julia> Base.lastindex(S::Squares) = length(S)
julia> Squares(23)[end]
529
对于多维 開始啦。/结束 索引如 a[3,开始,7],例如,您应该定义 firstindex(a,dim) 和 lastindex(a,dim) (默认调用 第一个 和 最后一次 上 轴(a,暗淡),分别)。
但是请注意,上面的_only_定义了 getindex,getindex具有一个整数索引。 用除 Int型 会抛出一个 方法;方法说没有匹配的方法。 为了支持索引与范围或向量 Int型s,必须写单独的方法:
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(10)[[3,4.,5]]
3-element Vector{Int64}:
9
16
25
虽然这开始支持更多的 一些内置类型支持的索引操作,仍然缺少相当多的行为。 这 正方形 序列开始看起来越来越像一个向量,因为我们已经添加了行为。 而不是自己定义所有这些行为,我们可以将其正式定义为 抽象阵列.
抽象数组
| 实现方法 | 简介 | |
|---|---|---|
|
返回包含以下维度的元组 |
|
|
(如果 |
|
|
(如果 |
|
*可选方法* |
*默认定义* |
*简介* |
|
|
返回任何一个 |
|
(如果 |
|
|
(如果 |
|
|
用标量定义 |
|
|
用标量定义 |
|
|
用标量定义 |
迭代 |
|
|
元素数量 |
|
|
返回具有相同形状和元素类型的可变数组 |
|
|
返回具有相同形状和指定元素类型的可变数组 |
|
|
返回具有相同元素类型和大小_dims的可变数组_ |
|
|
返回具有指定元素类型和大小的可变数组 |
*非传统指数* |
*默认定义* |
*简介* |
|
|
返回一个元组 |
|
|
返回具有指定索引的可变数组 |
|
|
返回一个类似于 |
如果一个类型被定义为 抽象阵列,它继承了一组非常大的丰富行为,包括构建在单元素访问之上的迭代和多维索引。 查看 数组手册页及 Julia Base section获取更多支持的方法。
定义一个关键部分 抽象阵列 子类型为 索引样式. 由于索引是数组的重要组成部分,并且经常发生在热循环中,因此尽可能高效地进行索引和索引分配非常重要。 数组数据结构通常以两种方式之一定义:要么只使用一个索引(线性索引)最有效地访问其元素,要么内在地访问为每个维度指定的索引的元素。 这两种模式被Julia确定为 索引线() 和 IndexCartesian(). 将线性索引转换为多个索引下标通常非常昂贵,因此这提供了一种基于特征的机制来为所有数组类型启用高效的泛型代码。
这种区分决定了类型必须定义哪些标量索引方法。 索引线() 数组很简单:只需定义 getindex(A::ArrayType,i::Int). 当数组随后使用多维索引集索引时,回退 getindex(A::AbstractArray,I...) 有效地将索引转换为一个线性索引,然后调用上述方法。 IndexCartesian() 另一方面,数组需要为每个支持的维度定义方法 ndims(A) Int型 指数。 例如, [医麻雀]从 [医]麻雀 标准库模块,只支持两个维度,所以它只是定义 getindex(A::SparseMatrixCSC,i::Int,j::Int). 同样适用于 setindex!.
回到上面的正方形序列,我们可以将其定义为 抽象阵列{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
请注意,指定的两个参数是非常重要的 抽象阵列;第一个定义了 eltype,eltype,而第二个定义了 ndims. 这个超类型和这三个方法就是它所需要的 SquaresVector碌录潞陆 要成为一个可迭代的,可索引的,完全功能的数组:
julia> s = SquaresVector(4)
4-element SquaresVector:
1
4
9
16
julia> s[s .> 8]
2-element Vector{Int64}:
9
16
julia> s + s
4-element Vector{Int64}:
2
8
18
32
julia> sin.(s)
4-element Vector{Float64}:
0.8414709848078965
-0.7568024953079282
0.4121184852417566
-0.2879033166650653
作为一个更复杂的例子,让我们定义我们自己的玩具N维稀疏类数组类型建立在 Dict,Dict:
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);
julia>SparseArray(::类型{T},酒窝::NTuple{N,Int})在哪里 {T,N} =SparseArray{T,N}(Dict{NTuple{N,Int},T}(),dims);
朱莉娅>基地。尺寸(A::SparseArray)=a.dims
朱莉娅>基地。类似(A::SparseArray,::Type{T},dims::Dims)哪里 {T} =SparseArray(T,dims)
朱莉娅>基地。getindex(A::SparseArray{T,N},I::瓦拉格{Int,N})在哪里 {T,N} =获取(a.数据,I,零(T))
朱莉娅>基地。setindex!(A::SparseArray{T,N},v,I::瓦拉格{Int,N})在哪里 {T,N} =(A.数据[I]=v)
请注意,这是一个 [医]索引 数组,所以我们必须手动定义 getindex,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); A
3×3 SparseArray{Float64, 2}:
1.0 4.0 7.0
2.0 5.0 8.0
3.0 6.0 9.0
索引an的结果 抽象阵列 本身可以是一个数组(例如,当由一个 抽象范围). 该 抽象阵列 回退方法使用 类似的分配 阵列 适当的大小和元素类型,这是使用上面描述的基本索引方法填写的。 但是,在实现数组包装器时,您经常希望结果也被包装:
julia> A[1:2,:]
2×3 SparseArray{Float64, 2}:
1.0 4.0 7.0
2.0 5.0 8.0
在这个例子中,它是通过定义来完成的 基地。类似(A::SparseArray,::Type{T},dims::Dims)其中T 以创建适当的包装数组。 (注意,虽然 类似的 支持1-和2-参数形式,在大多数情况下,您只需要专门化3-参数形式。)为了使这项工作起作用,重要的是 [医]麻雀 是可变的(支持 setindex!). 定义 类似的, getindex,getindex 和 setindex! 为 [医]麻雀 也使得有可能 副本阵列:
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中定义的大多数方法用于 [医]抽象:
julia> A[SquaresVector(3)]
3-element SparseArray{Float64, 1}:
1.0
4.0
9.0
julia> sum(A)
45.0
如果您正在定义一个允许非传统索引(索引以1以外的东西开始)的数组类型,则应该专门化 轴心. 你也应该专攻 类似的使 暗淡无光 参数(通常为 暗淡无光 size-tuple)可以接受 N.抽象,抽象 对象,也许是范围类型 Ind 你自己的设计。 有关详细信息,请参阅 带有自定义索引的数组。
跨步阵列
| 实现方法 | 简介 | |
|---|---|---|
|
以元组的形式返回每个维度中相邻元素之间的内存距离(元素数)。 如果 |
|
|
返回数组的本机地址。 |
|
|
返回数组中连续元素之间的步幅。 |
|
*可选方法* |
*默认定义* |
*简介* |
|
|
返回维度k中相邻元素之间的内存距离(元素数)。 |
跨步数组是 抽象阵列 其条目以固定步长存储在内存中。 如果数组的元素类型与BLAS兼容,跨步数组可以利用BLAS和LAPACK例程来实现更有效的线性代数例程。 用户定义的跨步数组的一个典型示例是包装标准的数组 阵列 具有附加结构。
警告:如果底层存储实际上没有跨步,请不要实现这些方法,因为这可能导致不正确的结果或分段错误。
以下是一些示例,以演示哪些类型的数组是跨步的,哪些不是:
1:5 # not strided (there is no storage associated with this array.)
Vector(1:5) # is strided with strides (1,)
A = [1 5; 2 6; 3 7; 4 8] # is strided with strides (1,4)
V = view(A, 1:2, :) # is strided with strides (1,4)
V = view(A, 1:2:3, 1:2) # is strided with strides (2,4)
V = view(A, [1,2,4], :) # is not strided, as the spacing between rows is not fixed.
定制广播
| 实现方法 | 简介 |
|---|---|
|
的广播行为 |
|
输出容器的分配 |
*可选方法* |
|
|
混合样式的优先级规则 |
|
《 |
|
转换/转换 |
*绕过默认机械* |
|
|
自定义实现 |
|
自定义实现 |
|
自定义实现 |
|
复盖融合表达式中的默认惰性行为 |
|
复盖懒惰广播轴的计算 |
广播是由一个明确的调用触发的 广播 或 广播!,或隐式地通过"点"操作,如 A.+b 或 f.(x,y). 任何有 轴心和支持索引可以作为参数参与广播,默认情况下结果存储在 阵列. 这个基本框架有三种主要的扩展方式:
*确保所有参数支持广播 *为给定的参数集选择适当的输出数组 *为给定的参数集选择有效的实现
并非所有类型都支持 轴心 和索引,但很多是方便允许在广播。 该 基地。广播电视函数被调用在每个参数广播,允许它返回不同的东西,支持 轴心 和索引。 默认情况下,这是所有人的标识函数 抽象阵列s和 电话号码他们已经支持了 轴心 和索引。
如果一个类型的作用类似于"0维标量"(单个对象),而不是作为广播的容器,那么应该定义以下方法:
Base.broadcastable(o::MyType) = Ref(o)
自定义类数组类型可以专门化 基地。广播电视 定义它们的形状,但它们应该遵循 收集(基地。broadcastable(x))==收集(x). 一个值得注意的例外是 抽象字符串;字符串是特殊大小写的,可以作为标量来进行广播,即使它们是其字符的可迭代集合(参见 字符串更多)。
接下来的两个步骤(选择输出数组和实现)取决于确定给定参数集的单个答案。 广播必须采取所有不同类型的参数,并将它们折叠到一个输出数组和一个实现。 广播称这个单一答案为"风格"。 每个broadcastable对象都有自己的首选样式,并且使用类似促销的系统将这些样式组合成一个单一的答案-"目标样式"。
广播风格
基地。广播电视 是派生所有广播样式的抽象类型。 当用作函数时,它有两种可能的形式,一元(单参数)和二元。 一元变体声明您打算实现特定的广播行为和/或输出类型,并且不希望依赖默认回退 广播。DefaultArrayStyle.
要复盖这些默认值,您可以定义自定义 广播电视 为您的对象:
struct MyStyle <: Broadcast.BroadcastStyle end
Base.BroadcastStyle(::Type{<:MyType}) = MyStyle()
在某些情况下,不必定义可能会很方便 我的名字,在这种情况下,您可以利用其中一个通用广播包装器:
* 基地。BroadcastStyle(::类型{<:MyType})=广播。风格{MyType}() 可用于任意类型。
* 基地。BroadcastStyle(::类型{<:MyType})=广播。[医]阵列式{MyType}() 是首选,如果 [医]类型 是一个 抽象阵列.
*为 [医]抽象 只支持某个维度,创建一个子类型 广播。[医]抽象派{N} (见下文)。
当您的广播操作涉及多个参数时,单个参数样式会组合在一起以确定单个参数 德斯特尔 它控制输出容器的类型。 有关更多详细信息,请参阅 下面。
选择适当的输出数组
为每个广播操作计算广播样式,以允许调度和专业化。 结果数组的实际分配由 类似的,使用广播对象作为其第一个参数。
Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType})
回退定义为
similar(bc::Broadcasted{DefaultArrayStyle{N}}, ::Type{ElType}) where {N,ElType} =
similar(Array{ElType}, axes(bc))
但是,如果需要,您可以专门研究任何或所有这些参数。 最后的论点 公元前 是一个(可能融合的)广播操作的懒惰表示,一个 广播 对象。 出于这些目的,包装器最重要的字段是 f 和 阿格斯,分别描述函数和参数列表。 请注意,参数列表可以-而且经常-包括其他嵌套 广播 包装纸。
对于一个完整的示例,假设您创建了一个类型, [医]阵列,存储数组和单个字符:
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, "'")
你可能需要广播来保存 查尔 "元数据"。 首先我们定义
Base.BroadcastStyle(::Type{<:ArrayAndChar}) = Broadcast.ArrayStyle{ArrayAndChar}()
这意味着我们还必须定义一个相应的 类似的 方法:
function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{ArrayAndChar}}, ::Type{ElType}) where ElType
# Scan the inputs for the ArrayAndChar:
A = find_aac(bc)
# Use the char field of A to create the output
ArrayAndChar(similar(Array{ElType}, axes(bc)), A.char)
end
"`A = find_aac(As)` returns the first ArrayAndChar among the arguments."
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(::Tuple{}) = nothing
find_aac(a::ArrayAndChar, rest) = a
find_aac(::Any, rest) = find_aac(rest)
从这些定义中,可以获得以下行为:
julia> a = ArrayAndChar([1 2; 3 4], 'x')
2×2 ArrayAndChar{Int64, 2} with char 'x':
1 2
3 4
julia> a .+ 1
2×2 ArrayAndChar{Int64, 2} with char 'x':
2 3
4 5
julia> a .+ [5,10]
2×2 ArrayAndChar{Int64, 2} with char 'x':
6 7
13 14
使用自定义实现扩展广播
一般来说,一个广播操作由一个懒 广播 容器,保存要与其参数一起应用的函数。 这些参数本身可能更加嵌套 广播 容器,形成待评估的大表达式树。 的嵌套树 广播 容器由隐式点语法直接构造; 5 .+ 2.*x 是瞬态表示的 广播(+,5,广播(*,2,x)) 例如。 这对用户是不可见的,因为它是通过调用立即实现的。 副本,但正是这个容器为自定义类型的作者提供了broadcast可扩展性的基础。 然后,内置的广播机器将根据参数确定结果类型和大小,分配它,然后最后复制实现 广播 使用默认值反对它 收到!(::抽象阵列,::广播) 方法。 内置后备 广播 和 广播! 方法类似地构造瞬态 广播 操作的表示,以便它们可以遵循相同的代码路径。 这允许自定义数组实现提供自己的 收到! 专业化定制和优化广播. 这再次由计算的广播样式确定。 这是操作的一个重要部分,它被存储为 广播 型,允许调度和专业化。
对于某些类型,跨嵌套广播级别"融合"操作的机器不可用,或者可以更有效地增量完成。 在这种情况下,您可能需要或想要评估 x.*(x.+ 1) 好像是写出来的 广播(*,x,广播(+,x,1)),其中内操作在处理外操作之前被评估。 这种急切的操作直接由一点间接支持;而不是直接构造 广播 对象,Julia降低融合表达式 x.*(x.+ 1) 到 广播。广播(*,x,广播。广播(+,x,1)). 现在,默认情况下, 广播 只要打电话给 广播 构造函数来创建融合表达式树的惰性表示,但您可以选择为函数和参数的特定组合复盖它。
作为一个例子,内置 抽象,抽象 对象使用此机制来优化广播表达式的片段,这些表达式可以纯粹根据开始,步骤和长度(或停止)进行快速评估,而不是计算每个元素。 就像其他机器一样, 广播 还计算并公开其参数的组合广播样式,因此而不是专门研究 广播(f,args...),你可以专攻 广播(::DestStyle,f,args...) 用于样式、函数和参数的任意组合。
例如,以下定义支持范围的否定:
broadcasted(::DefaultArrayStyle{1}, ::typeof(-), r::OrdinalRange) = range(-first(r), step=-step(r), length=length(r))
扩展就地广播
就地广播可以通过定义适当的支持 收到!(dest,bc::广播) 方法。 因为你可能想专攻 德斯特 或特定亚型的 公元前,为了避免包之间的歧义,我们建议使用以下约定。
如果你想专注于一种特定的风格 德斯特尔,定义用于
copyto!(dest, bc::Broadcasted{DestStyle})
或者,使用此表单,您还可以专门研究 德斯特.
如果相反,你想专注于目标类型 [医]变形型 没有专门研究 德斯特尔,那么你应该定义一个具有以下签名的方法:
copyto!(dest::DestType, bc::Broadcasted{Nothing})
这利用了 收到! 将包装器转换为 广播{Nothing}. 因此,专门从事 [医]变形型 优先级低于专门用于 德斯特尔.
同样,您可以使用 复制(::广播) 方法。
编写二进制广播规则
优先级规则由二进制定义 广播电视 电话:
Base.BroadcastStyle(::Style1, ::Style2) = Style12()
哪里 风格12 是 广播电视 您要选择涉及以下参数的输出 风格1 和 风格2. 例如,
Base.BroadcastStyle(::Broadcast.Style{Tuple}, ::Broadcast.AbstractArrayStyle{0}) = Broadcast.Style{Tuple}()
表示 元组 在零维数组上"获胜"(输出容器将是一个元组)。 值得注意的是,您不需要(也不应该)定义此调用的两个参数顺序;定义一个是足够的,无论用户以什么顺序提供参数。
为 抽象阵列 类型,定义 广播电视 取代后备选择, 广播。DefaultArrayStyle. DefaultArrayStyle 而抽象的超类型, [医]抽象派,将维数存储为类型参数,以支持具有固定维数要求的专用数组类型。
DefaultArrayStyle "输"给任何其他人 [医]抽象派 这是由于以下方法而定义的:
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)(Val(max(M, N)))
你不需要写二进制 广播电视 规则,除非要为两个或多个非建立优先级-DefaultArrayStyle 类型。
如果您的数组类型确实有固定的维数要求,那么您应该子类型 [医]抽象派. 例如,稀疏数组代码具有以下定义:
struct SparseVecStyle <: Broadcast.AbstractArrayStyle{1} end
struct SparseMatStyle <: Broadcast.AbstractArrayStyle{2} end
Base.BroadcastStyle(::Type{<:SparseVector}) = SparseVecStyle()
Base.BroadcastStyle(::Type{<:SparseMatrixCSC}) = SparseMatStyle()
每当你子类型 [医]抽象派,您还需要通过为您的样式创建一个构造函数来定义组合维度的规则 瓦尔(N) 争论。 例如:
SparseVecStyle(::Val{0}) = SparseVecStyle()
SparseVecStyle(::Val{1}) = SparseVecStyle()
SparseVecStyle(::Val{2}) = SparseMatStyle()
SparseVecStyle(::Val{N}) where N = Broadcast.DefaultArrayStyle{N}()
这些规则表明,一个 [医]麻雀式 用0维或1维数组产生另一个 [医]麻雀式,它与二维数组的组合产生一个 [医]麻雀式,而任何更高维度的东西都回落到密集的任意维框架。 这些规则允许广播保留导致一维或二维输出的操作的稀疏表示,但会产生 阵列 对于任何其他维度。
实例属性
| 实现方法 | 默认定义 | 简介 |
|---|---|---|
|
|
返回属性的元组( |
|
|
返回物业 |
|
|
设置属性 |
有时,希望更改最终用户与对象字段的交互方式。 可以通过重载在用户和代码之间提供额外的抽象层,而不是授予对类型字段的直接访问权限 对象。领域. 属性是什么用户_sees_的对象,字段什么对象_actually is_。
默认情况下,属性和字段是相同的。 但是,此行为可以更改。 例如,以平面中某个点的这种表示形式为例https://en.wikipedia.org/wiki/Polar_coordinate_system[极坐标]:
julia> mutable struct Point
r::Float64
ϕ::Float64
end
julia> p = Point(7.0, pi/4)
Point(7.0, 0.7853981633974483)
如上表中所述点访问 p.r 是一样的 getproperty(p,:r) 默认情况下与 盖菲尔德(p,:r):
julia> propertynames(p)
(:r, :ϕ)
julia> getproperty(p, :r), getproperty(p, :ϕ)
(7.0, 0.7853981633974483)
julia> p.r, p.ϕ
(7.0, 0.7853981633974483)
julia> getfield(p, :r), getproperty(p, :ϕ)
(7.0, 0.7853981633974483)
但是,我们可能希望用户不知道 点 将坐标存储为 r 和 ϕ (字段),而是与 x 和 y (属性)。 可以定义第一列中的方法以添加新功能:
julia> Base.propertynames(::Point, private::Bool=false) = private ? (:x, :y, :r, :ϕ) : (:x, :y)
julia> function Base.getproperty(p::Point, s::Symbol)
if s === :x
return getfield(p, :r) * cos(getfield(p, :ϕ))
elseif s === :y
return getfield(p, :r) * sin(getfield(p, :ϕ))
else
# This allows accessing fields with p.r and p.ϕ
return getfield(p, s)
end
end
julia> function Base.setproperty!(p::Point, s::Symbol, f)
if s === :x
y = p.y
setfield!(p, :r, sqrt(f^2 + y^2))
setfield!(p, :ϕ, atan(y, f))
return f
elseif s === :y
x = p.x
setfield!(p, :r, sqrt(x^2 + f^2))
setfield!(p, :ϕ, atan(f, x))
return f
else
# This allow modifying fields with p.r and p.ϕ
return setfield!(p, s, f)
end
end
重要的是 盖菲尔德 和 塞特菲尔德 内使用 获得财产 和 setproperty! 而不是点语法,因为点语法会使函数递归,这可能导致类型推断问题。 我们现在可以试用新功能了:
julia> propertynames(p)
(:x, :y)
julia> p.x
4.949747468305833
朱莉娅>p.y=4.0
4.0
朱莉娅>p.r
6.363961030678928
最后,值得注意的是,在Julia中很少添加这样的实例属性,通常只有在有充分理由这样做的情况下才应该这样做。
四舍五入
| 实现方法 | 默认定义 | 简介 |
|---|---|---|
|
无 |
圆形 |
|
|
圆形 |
为了支持对新类型的舍入,通常定义单个方法就足够了 round(x::ObjType,r::RoundingMode). 通过的舍入模式确定值应在哪个方向舍入。 最常用的舍入模式是 RoundNearest拢潞, 圆托泽罗, 四舍五入,而 综述,因为这些舍入模式用于一个参数的定义 圆形、方法和 [医], 楼层,而 齐尔,分别。
在某些情况下,可以定义一个三参数 圆形 比转换后的双参数方法更准确或更有效的方法。 在这种情况下,除了定义两个自变量方法之外,还可以定义三个自变量方法。 如果无法将舍入的结果表示为类型的对象 T,那么三个参数方法应该抛出一个 N.恐怖,恐怖.
例如,如果我们有一个 间隔时间 表示类似于以下可能值的范围的类型https://github.com/JuliaPhysics/Measurements.jl,我们可以使用以下内容定义该类型的四舍五入
julia> struct Interval{T}
min::T
max::T
end
julia> Base.round(x::Interval, r::RoundingMode) = Interval(round(x.min, r), round(x.max, r))
julia> x = Interval(1.7, 2.2)
Interval{Float64}(1.7, 2.2)
julia> round(x)
Interval{Float64}(2.0, 2.0)
julia> floor(x)
Interval{Float64}(1.0, 2.0)
julia> ceil(x)
Interval{Float64}(2.0, 3.0)
朱莉娅>trunc(x)
间隔时间{Float64}(1.0, 2.0)