Engee 文档

接口

Julia中的大部分特性和扩展性都是由于一组非正式的接口。 当扩展几个特定方法以使用自定义类型时,此类型的对象不仅可以接收适当的功能,还可以用于为形成行为模型而编写的其他方法中。

迭代

总是需要两种强制性方法。

强制性方法 简短描述

'迭代(iter)`

返回第一个元素和初始状态的元组,或者 `nothing',如果空

'迭代(iter,state)`

返回下一个元素和下一个状态的元组,或者如果没有剩余元素,则返回"nothing"。

还有其他几种方法应该在具体情况下确定。 请注意,您应该始终至少定义一个’Base’方法。IteratorSize(IterType)和`length(iter),因为默认为`Base。IteratorSize(IterType)定义为`Base。HasLength()

方法 何时应该定义此方法? 默认定义 简短描述

'基地。IteratorSize(IterType)'

如果默认值不适合

"基地。长()`

'基地之一。HasLength`)','Base.哈斯夏普{N}(),`基。IsInfinite)'或’基。SizeUnknown()`视情况而定

'长度(iter)`

如果’基地。IteratorSize`)'返回’Base。HasLength`)'或'Base。哈斯夏普{N}()`

(不确定)

元素的数量,如果已知

'尺寸(iter,[暗淡)`]

如果’基地。IteratorSize`)'返回'Base。哈斯夏普{N}()`

(不确定)

每个维度中的元素数(如果已知)

'基地。IteratorEltype(迭代类型’

如果默认值不适合

"基地。HasEltype()`

或’基地。EltypeUnknown`)`,或’基地。HasEltype()`视情况而定

'eltype(迭代类型’

如果默认值不适合

"任何`

`Iterate()`返回的元组的第一个条目的类型

'基地。isdone(iter,[state])`

*必须*确定迭代器是否正在监视状态

"失踪`

指定完成迭代器的快速路径。 如果未定义有状态迭代器,诸如`isempty()`和`zip()'之类的就绪检查函数可能会更改迭代器并导致错误。

使用函数实现顺序迭代 '迭代'。 Julia迭代器可以跟踪对象外部的迭代状态,而不是在对象迭代时更改对象。 迭代的返回值始终是值和状态的元组,或者如果没有剩余元素,则为"nothing"。 状态对象将在下一次迭代时传递回迭代函数,并且通常被认为是对迭代对象私有的实现细节。

此函数定义的任何对象都是可迭代的,可以在 应用迭代的许多功能。 它也可以直接在循环中使用。 'for',因为语法

for item in iter   # или "for item = iter"
    # тело
end

转换为

next = iterate(iter)
while next !== nothing
    (item, 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 item in Squares(7)
           println(item)
       end
1
4
9
16
25
36
49

您可以使用许多可迭代集合的内置方法,例如 `in''总和':

julia> 25 in Squares(10)
true

julia> sum(Squares(100))
338350

还有其他几种方法可以扩展为Julia提供有关此可迭代集合的更多信息。 我们知道’Squares’序列中的元素将始终是’Int’类型。 扩展方法 'eltype',这些信息可以传递给Julia,以便在更复杂的方法中创建更专门的代码。 我们也知道序列中元素的数量,所以我们也可以扩展函数 '长度':

julia> Base.eltype(::Type{Squares}) = Int # Обратите внимание, что это определено для типа

朱莉娅>基地。长度(S::Squares)=S.count

现在Julia需要使用该方法将所有元素收集到数组中 collect',Julia可以预先选择'+Vector{Int}+'正确的尺寸,而不是将每个项目发送到 '推!'使用'+Vector’函数{Any}+:

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中有一个常见的模式:一小组必要的方法定义了一个实现许多非标准行为的非正式接口。 在某些情况下,当类型知道可以在其特定情况下使用更有效的算法时,需要进一步专门化这些行为。

此外,通过使用函数进行迭代,允许集合以相反的顺序进行迭代通常很有用 '迭代器。反向(迭代器’。 但是,要实际支持反向迭代,迭代器类型’T’必须为`迭代器实现`iterate'。反向{T}. (考虑'r::迭代器。反向{T},'T’类型的基迭代器是’r.itr'。)在下面的’Squares’示例中,'迭代器。反'的方法来实现。{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

索引

实现的方法 简短描述

'getindex(X,i)`

X[i],索引访问,非标量`i’必须分配副本

"setindex!(X,v,i)`

`X[i]=v',索引赋值

'firstindex(X)`

`X[begin]'中使用的第一个索引

'lastindex(X)`

`X[end]`中使用的最后一个索引

对于上面显示的迭代`Squares`对象,您可以通过平方轻松计算序列的`i’th元素。 该过程可以表示为索引表达式’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(100)[23]
529

另外,要支持`s[begin]`和`s[end]'的语法,有必要定义 'firstindex'`lastindex'分别指定第一个和最后一个有效索引:

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

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

julia> Squares(23)[end]
529

对于’begin`/'end’的多维索引,例如在`a[3,begin,7]中,有必要定义`firstindex(a,dim)`和`lastindex(a,dim)(默认情况下,它们分别为`axes(a,dim)`调用`first`和`last')。

但是请注意,上面的示例只定义了一个函数 `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(10)[[3,4.,5]]
3-element Vector{Int64}:
  9
 16
 25

虽然它会支持更多的操作。 某些内置类型支持的索引仍然缺少一些功能。 随着行为被添加到向量中,这个"方块"序列开始越来越像向量。 而不是自己定义所有这些行为,它可以正式定义为子类型。 'AbstractArray'

抽象数组

实现的方法 简短描述

'尺寸(A)`

返回包含维度`A`的元组

'getindex(A,i::Int)`

(if`IndexLinear`)线性标量索引

'getindex(A,I::Vararg{Int, N})`

(if’IndexCartesian',where’N=ndims(A)`)N维标量索引

可选方法

默认定义

简短描述

'IndexStyle(::Type)`

'IndexCartesian()`

返回’IndexLinear`)`或’IndexCartesian()'。 请参阅下面的描述。

"setindex!(A,v,i::Int)`

(if’IndexLinear`)标量索引赋值

"setindex!(A,v,I::Vararg{Int, N})`

(if’IndexCartesian',where’N=ndims(A)`)N维标量索引赋值

'getindex(A,I...)`

它是根据标量函数"getindex`定义的

多维和非标量索引

'setindex!(A,X,I。..)`

它是根据标量函数"setindex!`

多维和非标量索引分配

`迭代'

它是根据标量函数"getindex`定义的

迭代

'长度(A)`

'prod(尺寸(A))`

元素数量

'类似(A)`

"类似(a,eltype(A),大小(A))"

返回具有相同形状和元素类型的可变数组。

'类似(A,::类型{S})`

'相似(A,S,尺寸(A))`

返回具有相同形状和指定元素类型的可变数组。

"类似(A,dims::Dims)`

'类似(A,eltype(A),dims)`

返回具有相同元素类型和_size维度的可变数组

'类似(A,::类型{S},dims::Dims)`

'数组{S}(undef,dims)`

返回具有指定元素类型和大小的可变数组

非传统索引

默认定义

简短描述

'轴(A)`

"地图(OneTo,尺寸(A))"

返回元组’AbstractUnitRange{<:Integer}'来自有效索引。 轴必须是索引的正确轴,即必须满足条件"轴"。(轴(A),1)==轴(A)`。

'类似(A,::类型{S},inds)`

'类似(A,S,Base.to_shape(inds))`

返回具有指定’inds’索引的可变数组(见下文)

'类似(T::Union{Type,Function},inds)`

'T(Base.to_shape(inds))`

返回一个类似于`T’的数组,指定索引为’inds'(见下文)

如果将类型定义为’AbstractArray`的子类型,则它继承了一组非常大的多样化功能,包括基于单例访问的迭代和多维索引。 有关支持的方法的详细信息,请参阅 专用于数组的手册页,并在 本节为茱莉亚基地

定义’AbstractArray’子类型的关键元素是类型 'IndexStyle'。 由于索引是数组的重要组成部分,并且经常在热循环中找到,因此尽可能高效地执行索引和索引分配非常重要。 数组数据结构通常以两种方式之一定义:要么它们只使用一个索引(线性索引)尽可能有效地访问它们的元素,要么它们在内部访问为每个维度设置索引的 Julia中的这两个选项被称为’IndexLinear`)`和’IndexCartesian()'。 将线性索引转换为多个下标通常是非常昂贵的操作,因此有一种基于功能的机制,允许您为所有类型的数组创建高效的通用代码。

这种差异决定了哪些标量索引方法应该确定类型。 数组’IndexLinear('很简单:定义`getindex(A::ArrayType,i::Int)`就足够了。 当数组随后使用多维索引集进行索引时,备份'+getindex(A::AbstractArray,I。..)+'将索引转换为单个线性索引,然后调用上述方法。 另一方面,'IndexCartesian()'数组需要使用`ndims(A)``Int`索引为每个支持的维度定义方法。 例如,类型 'SparseMatrixCSC'来自标准库模块’SparseArrays’仅支持两个维度,因此它简单地定义了’getindex(A::SparseMatrixCSC,i::Int,j::Int)。 这同样适用于 'setindex!`.

如果我们采取上面所示的正方形序列,它可以被定义为`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(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

朱莉娅>罪恶.(s)
4元素向量{Float64}:
  0.8414709848078965
 -0.7568024953079282
  0.4121184852417566
 -0.2879033166650653

让我们举一个更复杂的例子,并根据定义我们自己的模型N维稀疏数组类型 '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(::Type{T}, dims::NTuple{N,Int}) where {T,N} = SparseArray{T,N}(Dict{NTuple{N,Int}, T}(), dims);

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); 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`选择一个适当大小和类型的元素的`数组',使用上面描述的基本索引方法进行填充。 但是,在实现数组包装器时,通常要求结果也包含在包装器中。:

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

在上面的例子中,这是通过定义'Base来实现的。类似(A::SparseArray,::Type{T},dims::Dims)其中T创建相应的封闭数组。 (请注意,虽然"类似"支持一个和两个参数形式,但在大多数情况下,只有三个参数形式需要专门化。)为此,重要的是’SparseArray’是可变的(支持`setindex!). 通过定义’similar`,getindex’和’setindex!'对于’SparseArray,您可以使用函数复制数组 '复制':

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中为`AbstractArrays`定义的大多数方法:

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

julia> sum(A)
45.0

如果您正在定义允许非传统索引(索引以1以外的值开始)的数组类型,则需要专门化 '轴'。 你也应该专攻 'similar`,以便’dims’参数(通常是`Dims`大小的元组)可以接受`AbstractUnitRange`对象,可能是您自己设计的`Ind`范围类型。 有关更多信息,请参阅章节 带有自定义索引的数组

具有给定步骤的数组

实现的方法 简短描述

'大步(A)`

以元组的形式返回每个维度中相邻元素之间的内存距离(元素数)。 如果’A’是’AbstractArray{T,0}`,必须返回一个空元组。

'基地。unsafe_convert(::类型{Ptr{T}},A)`

返回数组自己的地址。

"基地。elsize(::类型{<:A})`

返回数组中连续元素之间的步长。

可选方法

默认定义

简短描述

'步幅(A,i::Int)`

'大步(a)[i]`

返回维度k中相邻元素之间的内存距离(元素数)。

具有指定步骤的数组是`AbstractArray`数组的子类型,其记录以固定步骤存储在内存中。 如果数组元素类型与BLAS兼容,则具有给定步骤的数组可以使用BLAS和LAPACK例程来实现更有效的线性代数过程。 具有指定步骤的用户定义数组的标准示例是将标准数组数组包含在附加结构中的数组。

警告。 如果基础存储没有指定的步骤,请不要实现这些方法,因为这可能导致不正确的结果或分段错误。

下面是具有和不具有指定步骤的数组类型的示例。

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], :)   # без заданного шага, так как расстояние между строками не является фиксированным.

设置广播

实现的方法 简短描述

'基地。BroadcastStyle(::类型{SrcType})=SrcStyle()`

进行"SrcType"类型广播

'基地。类似(bc::Broadcasted{DestStyle},::类型{ElType})`

分配输出容器

可选方法

"基地。BroadcastStyle(::Style1,::Style2)=Style12()`

混合样式的优先级规则

"基地。轴(x)`

根据方法声明`x`的索引 '轴(x’

"基地。广播(x)`

将`x’转换为带有`axes’的对象,并支持索引

绕过默认机制

'基地。复制(bc::广播{DestStyle})`

"广播"的自定义实现

'基地。收到!(dest,bc::广播{DestStyle})`

自定义实现`广播!'与’DestStyle’专业化

'基地。收到!(dest::DestType,bc::Broadcasted{Nothing})`

自定义实现`广播!'与’DestType’专业化

'基地。广播。广播(f,args...)`

复盖联接表达式中的默认延迟行为。

'基地。广播。实例化(bc::Broadcasted{DestStyle})`

重新定义延迟平移轴的计算

广播通过调用"广播"或"广播"显式激活!`或隐式地通过使用点操作如’A。+b’或’f.(x,y)'。 任何有 `axes'和索引启用,可以作为参数参与翻译。 默认情况下,结果存储在"数组"中。 这个基本平台有三种主要的可扩展方式:

  • 为所有论点提供翻译支持

  • 为一组给定的参数选择适当的输出数组

  • 为一组给定的参数选择一个有效的实现

并非所有类型都支持轴和索引,但许多类型可以在翻译中启用。 功能 '基地。为每个广播参数调用broadcastable`,它允许您返回支持"轴"和索引的其他内容。 默认情况下,这是所有`AbstractArray`和`Number`的标识函数-它们已经支持`axes`和索引。

如果类型应该充当"0维标量"(一个单独的对象),而不是用于翻译的容器,那么必须定义以下方法:

Base.broadcastable(o::MyType) = Ref(o)

它返回一个包含在0维容器中的参数 'Ref'。 例如,这样的包装器方法是为类型本身、函数和特殊的单个对象定义的,例如 '失踪'`无',和日期。

自定义类数组类型可以专门化’基地。broadcastable’来定义它们的形式,但它们必须遵循`collect(Base)的约定。broadcastable(x))==collect(x)`。 一个重要的例外是’AbstractString'。 字符串有一个特殊的情况作为标量用于翻译目的,尽管它们是其字符的可迭代集合(有关更多信息,请参阅 线)。

接下来的两个步骤(输出数组的选择和实现)取决于为给定的参数集定义单个解决方案。 广播应该采用所有不同类型的参数,并将它们简化为单个输出数组和单个实现。 在广播中,这种单一的解决方案被称为"风格"。 每个广播对象都有自己的首选样式,并将这些样式组合成一个单一的解决方案-"目标样式"-使用类似于促销的系统。

广播风格

"基地。BroadcastStyle’是所有广播样式都派生自的抽象类型。 当用作函数时,它有两种可能的形式:一元(单参数)和二元。 一元选项意味着您打算实现某种转换行为和/或输出类型,并且不希望使用默认回退类型。 '广播。DefaultArrayStyle'

要复盖这些默认设置,您可以为对象定义自定义`BroadcastStyle`类型。:

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

在某些情况下,不定义`MyStyle`是更合理的,那么可以使用常见的翻译包装器之一。:

  • '基地。BroadcastStyle(::类型{<:MyType})=广播。风格{MyType}()`可用于任意类型。

  • '基地。BroadcastStyle(::类型{<:MyType})=广播。[医]阵列式{MyType}如果`MyType`是`AbstractArray`,则'是首选选项。

  • 对于仅支持某个维度的’AbstractArrays`,创建'Broadcast的子类型。[医]抽象派{N}`(见下文)。

当在转换操作中使用多个参数时,各个参数的样式被组合以定义一个控制输出容器类型的"DestStyle"。 有关详细信息,请参阅 下面

选择适当的输出阵列

为每个广播操作计算广播样式,以允许调度和专业化。 结果数组的实际分配使用"类似"处理,其中翻译的对象用作第一个参数。

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

备份定义如下所示。

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

但是,如果需要,您可以专门化任何或所有这些参数。 最后一个参数’bc`是一个(可能组合的)广播操作的延迟表示,一个`Broadcasted’对象。 出于这些目的,最重要的shell字段是’f’和’args',它们分别描述函数和参数列表。 请注意,参数列表可以并且通常确实包含其他"广播"嵌套包装器。

举一个完整的例子:假设你创建了一个`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}()
# вывод

这意味着也应该定义相应的"类似"方法。:

function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{ArrayAndChar}}, ::Type{ElType}) where ElType
    # Сканирует входные данные для ArrayAndChar:
    A = find_aac(bc)
    # Использует поле символов A для создания вывода
    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)
# вывод
find_aac (generic function with 6 methods)

以下行为遵循这些定义。

julia> a = ArrayAndChar([1 2; 3 4], 'x')
2×2 ArrayAndChar{Int64, 2} with char 'x':
 1  2
 3  4

朱莉娅>一个。+ 1
2×2阵列{Int64, 2} 用char'x':
 2  3
 4  5

朱莉娅>一个。+ [5,10]
2×2阵列{Int64, 2} 用char'x':
  6   7
 13  14

使用自定义实现扩展翻译

通常,翻译操作由延迟的"广播"容器表示,该容器将应用的函数与参数一起存储。 这些参数本身可以是更深入嵌套的"广播"容器,形成一个用于评估的大型表达式树。 "广播"嵌套容器树是使用隐式点语法直接形成的。 例如’5。+ 2.x`暂时用`Broadcasted(+,5,Broadcasted(,2,x))表示。 这个动作对用户是不可见的,因为它是通过"复制"调用立即实现的,但是这个容器是自定义类型作者翻译可扩展性的基础。 然后内置的翻译机制将根据参数确定结果的类型和大小,选择它,然后使用默认的`copyto将`Broadcast`对象的实现复制到它中!'方法。(::AbstractArray,::Broadcasted)。 内置备份方法’广播’和’广播!'类似地,形成`广播`操作的临时表示以遵循相同的代码执行路径。 这允许自定义数组实现提供自己的专业化’copyto!'来设置和优化广播。 再次,该时刻由计算的广播样式确定。 这部分操作非常重要,它被存储为"广播"类型的第一个参数,它允许调度和专业化。

对于某些类型,在嵌套翻译级别"组合"操作的机制不可用,或者可以分阶段更有效地执行。 在这种情况下,可能需要评估表达式`x'。(x.1)`,就好像它被写成`broadcast(*,x,broadcast(,x,1))'一样,其中内部操作在执行外部操作之前计算。 直接间接支持这种类型的不复杂操作。 Julia不是直接创建`Broadcast`对象,而是降低了组合表达式’x.(x.1)`之前'广播。广播(*,x,广播。广播(,x,1))`。 现在,默认情况下,'broadcasted’只需调用’Broadcasted’构造函数来创建组合表达式树的延迟表示,但可以为特定的函数和参数组合复盖它。

例如,内置的"AbstractRange"对象使用此机制来优化翻译表达式的部分,这些部分可以直接根据开始,步骤和长度(或停止)进行评估,而不是计算每个单独的元素。 像所有其他机制一样’broadcasted’也计算并表示其参数的组合翻译风格,因此而不是专门化'broadcasted(f,args。..)`可以是专门的'广播(::DestStyle,f,args...)'表示样式、函数和参数的任意组合。

例如,以下定义支持范围的否定。

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

现场广播扩展

可以通过定义适当的"副本"来支持现场翻译!'方法。(dest,bc::Broadcasted)`。 由于可能需要专门化’dest`或`bc’的特定子类型,因此建议使用以下约定来避免包之间的歧义。

要专门化特定的"DestStyle"样式,请为

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

如有必要,您还可以使用此表单专门化`dest`类型。

如果你需要专门化目标类型’DestType’而不是专门化’DestStyle`,你应该定义一个具有以下签名的方法。

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

为此,copyto的备份实现! 使用',它将shell转换为'+'{Nothing}+. 因此,'DestType’的专门化比专门化`DestStyle’的方法具有更低的优先级。

同样,您可以使用`copy(::Broadcasted)`完全重新定义广播。

使用"广播"对象

实现诸如"复制"或"复制"之类的方法!`,您需要使用’Broadcast’包装器来计算每个元素。 有两种主要的方法来做到这一点。

  • '广播。flatten’将可能嵌套的操作重新计算为单个函数和平面参数列表。 您个人负责执行翻译表格的规则,但这只能在有限的情况下有用。

  • 迭代`axes(::Broadcast)`方法的`CartesianIndices`对象,并使用带有生成的`CartesianIndex’对象的索引来计算结果。

编写二进制翻译规则

优先级规则由对二进制`BroadcastStyle`的调用确定:

Base.BroadcastStyle(::Style1, ::Style2) = Style12()

其中’Style12’是为具有参数`Style1`和`Style2`的输出选择的’BroadcastStyle'。 例子::

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

表示’Tuple’优先于零维数组(输出容器将是一个元组)。 值得注意的是,没有必要(也不应该)为此调用定义两个参数顺序。 无论用户提供参数的顺序如何,定义一个参数就足够了。

对于"AbstractArray"类型,"BroadcastStyle"的定义取代了备份选项的选择。 '广播。DefaultArrayStyle'。 'DefaultArrayStyle’和抽象超类型’AbstractArrayStyle’将维度存储为类型参数,以支持具有固定维度要求的专用数组类型。

'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)(Val(max(M, N)))

您不需要编写二进制’BroadcastStyle’规则,除非您想为两个或更多不是`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`与null或一维数组结合会产生另一个’SparseVecStyle`,与二维数组结合会产生`SparseMatStyle',并且具有更高维度的所有内容都返回到任意维度的密集结构。 这些规则允许转换为导致一维或二维输出数据的操作保留稀疏表示,但这些操作会为任何其他维度生成"数组"。

实例属性

实现的方法 默认定义 简短描述

'propertynames(x::ObjType,private::Bool=false)`

'字段名(类型(x))`

返回对象`x`的属性元组(x.property)。 如果’private=true',则还返回应保持私有的属性的名称。

'getproperty(X::ObjType,s::Symbol)`

'getfield(x,s)`

返回对象’x’的属性’s'。 `x.s`调用`getproperty(x,:s)'。

'setproperty!(x::ObjType,s::Symbol,v)`

"塞特菲尔德!(x,s,v)`

将值`v’赋给对象`x`的属性`s`。 x.s=v’调用’setproperty!(x,:s,v)。 应返回值`v'。

有时希望更改最终用户与对象字段交互的方式。 您可以通过重载"对象"在用户和代码之间引入额外的抽象级别,而不是提供对类型字段的直接访问。场'。 属性是用户看到对象的方式,字段是对象实际表示的内容。

默认情况下,属性和字段匹配。 但是,此行为可以更改。 例如,取平面上点的表示在https://en.wikipedia.org/wiki/Polar_coordinate_system [极坐标]:

julia> mutable struct Point
           r::Float64
           ϕ::Float64
       end

julia>p=点(7.0,pi/4)
点(7.0,0.7853981633974483)

如上表所述,通过点符号"p.r"访问相当于调用"getproperty(p,:r)",默认情况下,它相当于"getfield(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)

但是,我们可能需要用户不知道’Point’中的坐标存储为’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
               # Позволяет обращаться к полям в форме p.r и 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
               # Позволяет изменять поля в форме p.r и p.ϕ
               return setfield!(p, s, f)
           end
       end

在"getproperty"和"setproperty"中使用"getfield"和"setfield"调用非常重要!'而不是点语法,因为点语法会使函数递归,这可能导致类型推断的问题。 现在您可以体验新功能。:

julia> propertynames(p)
(:x, :y)

julia> p.x
4.949747468305833

julia> p.y = 4.0
4.0

julia> p.r
6.363961030678928

总之,值得注意的是,在Julia中,实例属性很少以这种方式添加。 作为一项规则,必须有一个很好的理由。

四舍五入

实现的方法 默认定义 简短描述

'round(X::ObjType,r::RoundingMode)`

非也。

舍入’x’并返回结果。 如果可能的话,round方法应该返回与`x’相同类型的对象。

'round(T::Type,x::ObjType,r::RoundingMode)`

'转换(T,圆(x,r))`

四舍五入’x`,返回结果为’T'。

为了支持新类型的舍入,通常只需定义一个方法`round(x::ObjType,r::RoundingMode)就足够了。 传输的舍入模式确定值应在哪个方向被舍入。 最常用的舍入模式是’RoundNearest,'RoundToZero`,RoundDown`和`RoundUp,因为它们分别用于定义单个参数,round,method和`trunk`,`floor`和’ceil'。

在某些情况下,可以定义一个三参数的"圆"方法,它比一个两参数的方法更准确或更有效率,然后是一个转换。 在这种情况下,除了两参数方法之外,还可以定义三参数方法。 如果无法将舍入的结果表示为’T’类型对象,则具有三个参数的方法应引发’InexactError`错误。

例如,如果存在表示可能值范围的区间类型,例如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)

julia> trunc(x)
Interval{Float64}(1.0, 2.0)