Interfaces
الكثير من القوة والامتداد في جوليا يأتي من مجموعة من الواجهات غير الرسمية. من خلال توسيع بعض الطرق المحددة لتعمل مع نوع مخصص، فإن كائنات ذلك النوع لا تتلقى فقط تلك الوظائف، ولكنها قادرة أيضًا على أن تُستخدم في طرق أخرى مكتوبة لبناء بشكل عام على تلك السلوكيات.
Iteration
هناك طريقتان مطلوبتان دائمًا:
Required method | Brief description |
---|---|
iterate(iter) | Returns either a tuple of the first item and initial state or nothing if empty |
iterate(iter, state) | Returns either a tuple of the next item and next state or nothing if no items remain |
هناك العديد من الطرق الأخرى التي يجب تعريفها في بعض الظروف. يرجى ملاحظة أنه يجب عليك دائمًا تعريف واحدة على الأقل من Base.IteratorSize(IterType)
و length(iter)
لأن التعريف الافتراضي لـ Base.IteratorSize(IterType)
هو Base.HasLength()
.
Method | When should this method be defined? | Default definition | Brief description |
---|---|---|---|
Base.IteratorSize(IterType) | If default is not appropriate | Base.HasLength() | One of Base.HasLength() , Base.HasShape{N}() , Base.IsInfinite() , or Base.SizeUnknown() as appropriate |
length(iter) | If Base.IteratorSize() returns Base.HasLength() or Base.HasShape{N}() | (undefined) | The number of items, if known |
size(iter, [dim]) | If Base.IteratorSize() returns Base.HasShape{N}() | (undefined) | The number of items in each dimension, if known |
Base.IteratorEltype(IterType) | If default is not appropriate | Base.HasEltype() | Either Base.EltypeUnknown() or Base.HasEltype() as appropriate |
eltype(IterType) | If default is not appropriate | Any | The type of the first entry of the tuple returned by iterate() |
Base.isdone(iter, [state]) | Must be defined if iterator is stateful | missing | Fast-path hint for iterator completion. If not defined for a stateful iterator then functions that check for done-ness, like isempty() and zip() , may mutate the iterator and cause buggy behaviour! |
يتم تنفيذ التكرار المتسلسل بواسطة دالة iterate
. بدلاً من تعديل الكائنات أثناء التكرار عليها، قد تحتفظ المكررات في جوليا بحالة التكرار خارجيًا عن الكائن. قيمة الإرجاع من iterate هي دائمًا إما مجموعة من قيمة وحالة، أو nothing
إذا لم يتبق أي عناصر. سيتم تمرير كائن الحالة مرة أخرى إلى دالة iterate في التكرار التالي وعادة ما يعتبر تفصيلًا تنفيذيًا خاصًا بالكائن القابل للتكرار.
أي كائن يحدد هذه الوظيفة قابل للتكرار ويمكن استخدامه في many functions that rely upon iteration. يمكن أيضًا استخدامه مباشرة في حلقة for
نظرًا لأن الصيغة:
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)
مع تعريف iterate
فقط، فإن نوع Squares
قوي بالفعل. يمكننا التكرار على جميع العناصر:
julia> for item in Squares(7)
println(item)
end
1
4
9
16
25
36
49
يمكننا استخدام العديد من الطرق المدمجة التي تعمل مع القوائم القابلة للتكرار، مثل in
أو sum
:
julia> 25 in Squares(10)
true
julia> sum(Squares(100))
338350
هناك بعض الطرق الإضافية التي يمكننا توسيعها لإعطاء جوليا مزيدًا من المعلومات حول هذه المجموعة القابلة للتكرار. نحن نعلم أن العناصر في تسلسل Squares
ستكون دائمًا Int
. من خلال توسيع طريقة eltype
، يمكننا إعطاء هذه المعلومات لجوليا ومساعدتها في إنشاء كود أكثر تخصصًا في الطرق الأكثر تعقيدًا. نحن نعلم أيضًا عدد العناصر في تسلسلنا، لذا يمكننا توسيع length
أيضًا:
julia> Base.eltype(::Type{Squares}) = Int # Note that this is defined for the type
julia> Base.length(S::Squares) = S.count
الآن، عندما نطلب من جوليا collect
جميع العناصر في مصفوفة، يمكنها تخصيص Vector{Int}
بالحجم الصحيح بدلاً من push!
كل عنصر في 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
هذا نمط شائع جدًا في قاعدة جوليا: مجموعة صغيرة من الطرق المطلوبة تحدد واجهة غير رسمية تمكن العديد من السلوكيات الأكثر تعقيدًا. في بعض الحالات، قد ترغب الأنواع في تخصيص تلك السلوكيات الإضافية عندما تعرف أنه يمكن استخدام خوارزمية أكثر كفاءة في حالتها المحددة.
من المفيد أيضًا غالبًا السماح بالتكرار على مجموعة بترتيب عكسي عن طريق التكرار على Iterators.reverse(iterator)
. لدعم التكرار بترتيب عكسي، يحتاج نوع المكرر T
إلى تنفيذ iterate
لـ Iterators.Reverse{T}
. (بالنظر إلى 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(4)))
4-element Vector{Int64}:
16
9
4
1
Indexing
Methods to implement | Brief description |
---|---|
getindex(X, i) | X[i] , indexed access, non-scalar i should allocate a copy |
setindex!(X, v, i) | X[i] = v , indexed assignment |
firstindex(X) | The first index, used in X[begin] |
lastindex(X) | The last index, used in 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(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)
(التي تتضمن افتراضيًا استدعاء first
و last
على axes(a, dim)
، على التوالي).
لاحظ، مع ذلك، أن ما سبق يحدد فقط getindex
مع فهرس صحيح واحد. سيلقي الفهرسة باستخدام أي شيء غير Int
استثناء MethodError
قائلاً إنه لم يكن هناك طريقة مطابقة. لدعم الفهرسة باستخدام النطاقات أو المتجهات من 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
بينما بدأ هذا في دعم المزيد من indexing operations supported by some of the builtin types، لا يزال هناك عدد كبير من السلوكيات المفقودة. يبدو أن تسلسل Squares
بدأ يبدو أكثر فأكثر مثل متجه حيث أضفنا سلوكيات إليه. بدلاً من تعريف كل هذه السلوكيات بأنفسنا، يمكننا تعريفه رسميًا كنوع فرعي من AbstractArray
.
Abstract Arrays
Methods to implement | Brief description | |
---|---|---|
size(A) | Returns a tuple containing the dimensions of A | |
getindex(A, i::Int) | (if IndexLinear ) Linear scalar indexing | |
getindex(A, I::Vararg{Int, N}) | (if IndexCartesian , where N = ndims(A) ) N-dimensional scalar indexing | |
Optional methods | Default definition | Brief description |
IndexStyle(::Type) | IndexCartesian() | Returns either IndexLinear() or IndexCartesian() . See the description below. |
setindex!(A, v, i::Int) | (if IndexLinear ) Scalar indexed assignment | |
setindex!(A, v, I::Vararg{Int, N}) | (if IndexCartesian , where N = ndims(A) ) N-dimensional scalar indexed assignment | |
getindex(A, I...) | defined in terms of scalar getindex | Multidimensional and nonscalar indexing |
setindex!(A, X, I...) | defined in terms of scalar setindex! | Multidimensional and nonscalar indexed assignment |
iterate | defined in terms of scalar getindex | Iteration |
length(A) | prod(size(A)) | Number of elements |
similar(A) | similar(A, eltype(A), size(A)) | Return a mutable array with the same shape and element type |
similar(A, ::Type{S}) | similar(A, S, size(A)) | Return a mutable array with the same shape and the specified element type |
similar(A, dims::Dims) | similar(A, eltype(A), dims) | Return a mutable array with the same element type and size dims |
similar(A, ::Type{S}, dims::Dims) | Array{S}(undef, dims) | Return a mutable array with the specified element type and size |
Non-traditional indices | Default definition | Brief description |
axes(A) | map(OneTo, size(A)) | Return a tuple of AbstractUnitRange{<:Integer} of valid indices. The axes should be their own axes, that is axes.(axes(A),1) == axes(A) should be satisfied. |
similar(A, ::Type{S}, inds) | similar(A, S, Base.to_shape(inds)) | Return a mutable array with the specified indices inds (see below) |
similar(T::Union{Type,Function}, inds) | T(Base.to_shape(inds)) | Return an array similar to T with the specified indices inds (see below) |
إذا تم تعريف نوع كنوع فرعي من AbstractArray
، فإنه يرث مجموعة كبيرة جدًا من السلوكيات الغنية بما في ذلك التكرار والفهرسة متعددة الأبعاد المبنية على الوصول إلى العناصر الفردية. انظر إلى arrays manual page و Julia Base section لمزيد من الطرق المدعومة.
جزء أساسي في تعريف نوع فرعي من AbstractArray
هو IndexStyle
. نظرًا لأن الفهرسة هي جزء مهم جدًا من المصفوفة وغالبًا ما تحدث في حلقات ساخنة، من المهم جعل كل من الفهرسة والتعيين المفهرس فعالين قدر الإمكان. تُعرف هياكل بيانات المصفوفات عادةً بإحدى طريقتين: إما أنها تصل إلى عناصرها بأكثر الطرق كفاءة باستخدام فهرس واحد فقط (الفهرسة الخطية) أو أنها تصل بشكل جوهري إلى العناصر باستخدام فهارس محددة لكل بعد. يتم التعرف على هذين النمطين من قبل جوليا كـ 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
julia> sin.(s)
4-element Vector{Float64}:
0.8414709848078965
-0.7568024953079282
0.4121184852417566
-0.2879033166650653
كمثال أكثر تعقيدًا، دعنا نحدد نوع مصفوفة نادرة متعددة الأبعاد مبنية على 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
، نحن قادرون على تعريف 4d61726b646f776e2e436f64652822222c2022736574696e646578212229_40726566
، وبالتالي يمكننا تغيير المصفوفة:
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
The result of indexing an AbstractArray
can itself be an array (for instance when indexing by an AbstractRange
). The AbstractArray
fallback methods use similar
to allocate an Array
of the appropriate size and element type, which is filled in using the basic indexing method described above. However, when implementing an array wrapper you often want the result to be wrapped as well:
julia> A[1:2,:]
2×3 SparseArray{Float64, 2}:
1.0 4.0 7.0
2.0 5.0 8.0
في هذا المثال يتم تحقيق ذلك من خلال تعريف Base.similar(A::SparseArray, ::Type{T}, dims::Dims) where T
لإنشاء المصفوفة المغلفة المناسبة. (لاحظ أنه بينما يدعم similar
أشكال 1 و 2 من المعاملات، في معظم الحالات تحتاج فقط إلى تخصيص الشكل ذو 3 معاملات.) لكي يعمل هذا، من المهم أن تكون SparseArray
قابلة للتغيير (تدعم setindex!
). كما أن تعريف similar
و getindex
و setindex!
لـ SparseArray
يجعل من الممكن 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
بالإضافة إلى جميع الطرق القابلة للتكرار والفهرسة المذكورة أعلاه، يمكن لهذه الأنواع أيضًا التفاعل مع بعضها البعض واستخدام معظم الطرق المحددة في قاعدة جوليا لـ AbstractArrays
:
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
من تصميمك الخاص. لمزيد من المعلومات، انظر Arrays with custom indices.
Strided Arrays
Methods to implement | Brief description | |
---|---|---|
strides(A) | Return the distance in memory (in number of elements) between adjacent elements in each dimension as a tuple. If A is an AbstractArray{T,0} , this should return an empty tuple. | |
Base.unsafe_convert(::Type{Ptr{T}}, A) | Return the native address of an array. | |
Base.elsize(::Type{<:A}) | Return the stride between consecutive elements in the array. | |
Optional methods | Default definition | Brief description |
stride(A, i::Int) | strides(A)[i] | Return the distance in memory (in number of elements) between adjacent elements in dimension k. |
مصفوفة ذات خطوات هي نوع فرعي من AbstractArray
يتم تخزين مدخلاتها في الذاكرة بخطوات ثابتة. بشرط أن يكون نوع عنصر المصفوفة متوافقًا مع BLAS، يمكن لمصفوفة ذات خطوات استخدام روتينات BLAS و LAPACK لروتينات الجبر الخطي بشكل أكثر كفاءة. مثال نموذجي لمصفوفة ذات خطوات معرفة من قبل المستخدم هو تلك التي تغلف Array
قياسية مع هيكل إضافي.
تحذير: لا تقم بتنفيذ هذه الطرق إذا لم يكن التخزين الأساسي فعليًا متدرجًا، حيث قد يؤدي ذلك إلى نتائج غير صحيحة أو أخطاء في تقسيم الذاكرة.
إليك بعض الأمثلة لتوضيح أي نوع من المصفوفات هو مصفوفة متدرجة وأيها ليس كذلك:
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.
Customizing broadcasting
Methods to implement | Brief description |
---|---|
Base.BroadcastStyle(::Type{SrcType}) = SrcStyle() | Broadcasting behavior of SrcType |
Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType}) | Allocation of output container |
Optional methods | |
Base.BroadcastStyle(::Style1, ::Style2) = Style12() | Precedence rules for mixing styles |
Base.axes(x) | Declaration of the indices of x , as per axes(x) . |
Base.broadcastable(x) | Convert x to an object that has axes and supports indexing |
Bypassing default machinery | |
Base.copy(bc::Broadcasted{DestStyle}) | Custom implementation of broadcast |
Base.copyto!(dest, bc::Broadcasted{DestStyle}) | Custom implementation of broadcast! , specializing on DestStyle |
Base.copyto!(dest::DestType, bc::Broadcasted{Nothing}) | Custom implementation of broadcast! , specializing on DestType |
Base.Broadcast.broadcasted(f, args...) | Override the default lazy behavior within a fused expression |
Base.Broadcast.instantiate(bc::Broadcasted{DestStyle}) | Override the computation of the lazy broadcast's axes |
Broadcasting يتم تفعيله من خلال استدعاء صريح لـ broadcast
أو broadcast!
، أو بشكل غير مباشر من خلال عمليات "النقطة" مثل A .+ b
أو f.(x, y)
. أي كائن يحتوي على axes
ويدعم الفهرسة يمكن أن يشارك كحجة في البث، وبشكل افتراضي يتم تخزين النتيجة في Array
. هذا الإطار الأساسي قابل للتوسيع بثلاث طرق رئيسية:
- ضمان أن جميع الحجج تدعم البث
- اختيار مصفوفة الإخراج المناسبة لمجموعة المعطيات المعطاة
- اختيار تنفيذ فعال لمجموعة المعطيات المعطاة
لا تدعم جميع الأنواع axes
والفهرسة، ولكن العديد منها مريح للسماح في البث. يتم استدعاء دالة Base.broadcastable
على كل وسيط للبث، مما يسمح لها بإرجاع شيء مختلف يدعم axes
والفهرسة. بشكل افتراضي، هذه هي دالة الهوية لجميع AbstractArray
s و Number
s - فهي تدعم بالفعل axes
والفهرسة.
إذا كان النوع مقصودًا أن يعمل مثل "مقياس بعده 0" (كائن واحد) بدلاً من كونه حاوية للبث، فيجب تعريف الطريقة التالية:
Base.broadcastable(o::MyType) = Ref(o)
that returns the argument wrapped in a 0-dimensional Ref
container. For example, such a wrapper method is defined for types themselves, functions, special singletons like missing
and nothing
, and dates.
يمكن أن تتخصص أنواع المصفوفات المخصصة مثل Base.broadcastable
لتعريف شكلها، ولكن يجب أن تتبع القاعدة التي تنص على أن collect(Base.broadcastable(x)) == collect(x)
. استثناء ملحوظ هو AbstractString
؛ حيث يتم التعامل مع السلاسل كحالات خاصة لتتصرف كقيم فردية لأغراض البث على الرغم من أنها مجموعات قابلة للتكرار من شخصياتها (انظر Strings لمزيد من المعلومات).
الخطوتان التاليتان (اختيار مصفوفة الإخراج والتنفيذ) تعتمد على تحديد إجابة واحدة لمجموعة معينة من المعطيات. يجب على البث أخذ جميع الأنواع المتنوعة من معطياته وتقليصها إلى مصفوفة إخراج واحدة وتنفيذ واحد. يسمي البث هذه الإجابة الواحدة "نمط". كل كائن قابل للبث له نمطه المفضل، ويتم استخدام نظام مشابه للترقية لدمج هذه الأنماط في إجابة واحدة — "نمط الوجهة".
Broadcast Styles
Base.BroadcastStyle
هو النوع المجرد الذي تُشتق منه جميع أنماط البث. عند استخدامه كدالة، له شكلان ممكنان، أحادي (حجة واحدة) وثنائي. الشكل الأحادي ينص على أنك تنوي تنفيذ سلوك بث محدد و/أو نوع مخرجات، ولا ترغب في الاعتماد على الاحتياطي الافتراضي Broadcast.DefaultArrayStyle
.
لتجاوز هذه الإعدادات الافتراضية، يمكنك تعريف BroadcastStyle
مخصص لكائنك:
struct MyStyle <: Broadcast.BroadcastStyle end
Base.BroadcastStyle(::Type{<:MyType}) = MyStyle()
في بعض الحالات، قد يكون من الملائم عدم الحاجة إلى تعريف MyStyle
، وفي هذه الحالة يمكنك الاستفادة من أحد أغلفة البث العامة:
Base.BroadcastStyle(::Type{<:MyType}) = Broadcast.Style{MyType}()
يمكن استخدامه لأنواع عشوائية.Base.BroadcastStyle(::Type{<:MyType}) = Broadcast.ArrayStyle{MyType}()
مفضل إذا كانMyType
هوAbstractArray
.- لـ
AbstractArrays
التي تدعم فقط بُعدًا معينًا، قم بإنشاء نوع فرعي منBroadcast.AbstractArrayStyle{N}
(انظر أدناه).
عندما تتضمن عملية البث الخاصة بك عدة معلمات، يتم دمج أنماط المعلمات الفردية لتحديد DestStyle
واحد يتحكم في نوع حاوية الإخراج. لمزيد من التفاصيل، راجع below.
Selecting an appropriate output array
يتم حساب نمط البث لكل عملية بث للسماح بالتوزيع والتخصص. يتم التعامل مع التخصيص الفعلي لمصفوفة النتيجة بواسطة similar
، باستخدام كائن البث كأول وسيط له.
Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType})
تعريف الاحتياطي هو
similar(bc::Broadcasted{DefaultArrayStyle{N}}, ::Type{ElType}) where {N,ElType} =
similar(Array{ElType}, axes(bc))
ومع ذلك، إذا لزم الأمر يمكنك التخصص في أي من هذه الحجج أو جميعها. الحجة النهائية bc
هي تمثيل كسول لعملية بث (قد تكون مدمجة)، وهي كائن Broadcasted
. لأغراض هذه، فإن أهم الحقول في الغلاف هي 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
# 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
Extending broadcast with custom implementations
بشكل عام، يتم تمثيل عملية البث بواسطة حاوية كسولة Broadcasted
التي تحتفظ بالوظيفة التي سيتم تطبيقها جنبًا إلى جنب مع معطياتها. قد تكون تلك المعطيات نفسها حاويات Broadcasted
متداخلة أكثر، مما يشكل شجرة تعبير كبيرة يجب تقييمها. يتم بناء شجرة متداخلة من حاويات Broadcasted
مباشرة بواسطة بناء الجملة الضمني للنقطة؛ 5 .+ 2.*x
يتم تمثيلها مؤقتًا بواسطة Broadcasted(+, 5, Broadcasted(*, 2, x))
، على سبيل المثال. هذا غير مرئي للمستخدمين حيث يتم تحقيقه على الفور من خلال استدعاء copy
، ولكن هذه الحاوية هي التي توفر الأساس لقابلية التمديد للبث لمؤلفي الأنواع المخصصة. ستحدد الآلية المدمجة للبث بعد ذلك نوع النتيجة وحجمها بناءً على المعطيات، وتخصصها، ثم أخيرًا تنسخ تحقيق كائن Broadcasted
إليها باستخدام طريقة افتراضية copyto!(::AbstractArray, ::Broadcasted)
. تقوم الطرق المدمجة الاحتياطية broadcast
و broadcast!
بشكل مشابه ببناء تمثيل مؤقت لـ Broadcasted
للعملية حتى تتمكن من اتباع نفس مسار الشيفرة. وهذا يسمح لتنفيذات المصفوفات المخصصة بتوفير تخصيص copyto!
الخاص بها لتخصيص وتحسين البث. يتم تحديد ذلك مرة أخرى بواسطة نمط البث المحسوب. هذه جزء مهم جدًا من العملية بحيث يتم تخزينها كأول معلمة نوع من نوع Broadcasted
، مما يسمح بالتوجيه والتخصيص.
بالنسبة لبعض الأنواع، فإن الآلية لـ "دمج" العمليات عبر مستويات متداخلة من البث غير متاحة أو يمكن القيام بها بشكل أكثر كفاءة بشكل تدريجي. في مثل هذه الحالات، قد تحتاج أو ترغب في تقييم x .* (x .+ 1)
كما لو كان قد كُتب broadcast(*, x, broadcast(+, x, 1))
، حيث يتم تقييم العملية الداخلية قبل معالجة العملية الخارجية. هذه النوع من العمليات المتعجلة مدعوم مباشرة بواسطة بعض التوجيه؛ بدلاً من إنشاء كائنات Broadcasted
مباشرة، تقوم جوليا بتخفيض التعبير المدمج x .* (x .+ 1)
إلى Broadcast.broadcasted(*, x, Broadcast.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))
Extending in-place broadcasting
يمكن دعم البث في المكان من خلال تعريف الطريقة المناسبة copyto!(dest, bc::Broadcasted)
. نظرًا لأنك قد ترغب في التخصص إما في dest
أو في النوع الفرعي المحدد لـ bc
، لتجنب الغموض بين الحزم، نوصي بالاتفاقية التالية.
إذا كنت ترغب في التخصص في نمط معين DestStyle
، قم بتعريف طريقة لـ
copyto!(dest, bc::Broadcasted{DestStyle})
اختياريًا، يمكنك أيضًا التخصص في نوع dest
باستخدام هذا النموذج.
إذا كنت ترغب بدلاً من ذلك في التخصص في نوع الوجهة DestType
دون التخصص في DestStyle
، فيجب عليك تعريف طريقة بالتوقيع التالي:
copyto!(dest::DestType, bc::Broadcasted{Nothing})
هذا يستفيد من تنفيذ احتياطي لـ copyto!
الذي يحول الغلاف إلى Broadcasted{Nothing}
. وبالتالي، فإن التخصص على DestType
له أولوية أقل من الطرق التي تتخصص في DestStyle
.
بالمثل، يمكنك تجاوز البث غير المناسب تمامًا باستخدام طريقة copy(::Broadcasted)
.
Working with Broadcasted
objects
لتنفيذ مثل هذه الطريقة copy
أو copyto!
، بالطبع، يجب عليك العمل مع غلاف Broadcasted
لحساب كل عنصر. هناك طريقتان رئيسيتان للقيام بذلك:
Broadcast.flatten
يعيد حساب العملية المحتملة المتداخلة إلى دالة واحدة وقائمة مسطحة من المعاملات. أنت مسؤول عن تنفيذ قواعد شكل البث بنفسك، ولكن قد يكون هذا مفيدًا في حالات محدودة.- التكرار على
CartesianIndices
لـaxes(::Broadcasted)
واستخدام الفهرسة مع كائنCartesianIndex
الناتج لحساب النتيجة.
Writing binary broadcasting rules
تُحدد قواعد الأولوية بواسطة استدعاءات ثنائية BroadcastStyle
:
Base.BroadcastStyle(::Style1, ::Style2) = Style12()
حيث Style12
هو BroadcastStyle
الذي تريد اختياره للإخراجات التي تتضمن وسائط من Style1
و Style2
. على سبيل المثال،
Base.BroadcastStyle(::Broadcast.Style{Tuple}, ::Broadcast.AbstractArrayStyle{0}) = Broadcast.Style{Tuple}()
يشير إلى أن Tuple
"يفوز" على المصفوفات ذات الأبعاد الصفرية (ستكون حاوية الإخراج عبارة عن مجموعة). من الجدير بالذكر أنك لا تحتاج إلى (ويجب ألا) تعريف كلا ترتيبَي المعاملات في هذه المكالمة؛ فتعريف واحد يكفي بغض النظر عن ترتيب المعاملات الذي يقدمه المستخدم.
بالنسبة لأنواع AbstractArray
، فإن تعريف BroadcastStyle
يتجاوز الخيار الافتراضي، Broadcast.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
مع مصفوفات ذات أبعاد 0 أو 1 ينتج عنه SparseVecStyle
آخر، وأن دمجه مع مصفوفة ثنائية الأبعاد ينتج SparseMatStyle
، وأي شيء ذو أبعاد أعلى يعود إلى إطار العمل الكثيف ذي الأبعاد التعسفية. تسمح هذه القواعد للبث بالحفاظ على التمثيل النادر للعمليات التي تؤدي إلى مخرجات ذات بعد واحد أو بعدين، ولكنها تنتج Array
لأي أبعاد أخرى.
Instance Properties
Methods to implement | Default definition | Brief description |
---|---|---|
propertynames(x::ObjType, private::Bool=false) | fieldnames(typeof(x)) | Return a tuple of the properties (x.property ) of an object x . If private=true , also return property names intended to be kept as private |
getproperty(x::ObjType, s::Symbol) | getfield(x, s) | Return property s of x . x.s calls getproperty(x, :s) . |
setproperty!(x::ObjType, s::Symbol, v) | setfield!(x, s, v) | Set property s of x to v . x.s = v calls setproperty!(x, :s, v) . Should return v . |
أحيانًا، يكون من المرغوب فيه تغيير كيفية تفاعل المستخدم النهائي مع حقول كائن ما. بدلاً من منح الوصول المباشر إلى حقول النوع، يمكن توفير طبقة إضافية من التجريد بين المستخدم والشيفرة من خلال تحميل object.field
. الخصائص هي ما يراه المستخدم من الكائن، بينما الحقول هي ما هو عليه الكائن فعليًا.
بشكل افتراضي، الخصائص والحقول هي نفسها. ومع ذلك، يمكن تغيير هذا السلوك. على سبيل المثال، خذ هذا التمثيل لنقطة في مستوى في polar coordinates:
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)
والذي يكون بشكل افتراضي هو نفسه 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
# 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
من المهم استخدام getfield
و setfield
داخل getproperty
و setproperty!
بدلاً من بناء جملة النقطة، حيث إن بناء جملة النقطة سيجعل الدوال تتكرر بشكل ذاتي مما قد يؤدي إلى مشاكل في استنتاج النوع. يمكننا الآن تجربة الوظائف الجديدة:
julia> propertynames(p)
(:x, :y)
julia> p.x
4.949747468305833
julia> p.y = 4.0
4.0
julia> p.r
6.363961030678928
أخيرًا، من الجدير بالذكر أن إضافة خصائص الحالة بهذه الطريقة نادرًا ما يتم في جوليا ويجب عمومًا أن يتم فقط إذا كانت هناك أسباب وجيهة للقيام بذلك.
Rounding
Methods to implement | Default definition | Brief description |
---|---|---|
round(x::ObjType, r::RoundingMode) | none | Round x and return the result. If possible, round should return an object of the same type as x |
round(T::Type, x::ObjType, r::RoundingMode) | convert(T, round(x, r)) | Round x , returning the result as a T |
لدعم التقريب على نوع جديد، يكفي عادةً تعريف الطريقة الواحدة round(x::ObjType, r::RoundingMode)
. تحدد وضعية التقريب الممررة في أي اتجاه يجب تقريب القيمة. أوضاع التقريب الأكثر استخدامًا هي RoundNearest
و RoundToZero
و RoundDown
و RoundUp
، حيث تُستخدم هذه الأوضاع في تعريفات الطريقة ذات الحجة الواحدة round
، و trunc
و floor
و ceil
، على التوالي.
في بعض الحالات، من الممكن تعريف طريقة round
ذات ثلاثة معطيات تكون أكثر دقة أو أداءً من الطريقة ذات المعطيين المتبوعة بالتحويل. في هذه الحالة، من المقبول تعريف طريقة المعطيات الثلاثة بالإضافة إلى طريقة المعطيات الاثنين. إذا كان من المستحيل تمثيل النتيجة المدورة ككائن من النوع T
، فيجب على طريقة المعطيات الثلاثة أن ترمي InexactError
.
على سبيل المثال، إذا كان لدينا نوع Interval
الذي يمثل نطاقًا من القيم الممكنة مشابهًا لـ 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)