Interfaces

الكثير من القوة والامتداد في جوليا يأتي من مجموعة من الواجهات غير الرسمية. من خلال توسيع بعض الطرق المحددة لتعمل مع نوع مخصص، فإن كائنات ذلك النوع لا تتلقى فقط تلك الوظائف، ولكنها قادرة أيضًا على أن تُستخدم في طرق أخرى مكتوبة لبناء بشكل عام على تلك السلوكيات.

Iteration

هناك طريقتان مطلوبتان دائمًا:

Required methodBrief 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().

MethodWhen should this method be defined?Default definitionBrief description
Base.IteratorSize(IterType)If default is not appropriateBase.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 appropriateBase.HasEltype()Either Base.EltypeUnknown() or Base.HasEltype() as appropriate
eltype(IterType)If default is not appropriateAnyThe type of the first entry of the tuple returned by iterate()
Base.isdone(iter, [state])Must be defined if iterator is statefulmissingFast-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 implementBrief 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 قائلاً إنه لم يكن هناك طريقة مطابقة. لدعم الفهرسة باستخدام النطاقات أو المتجهات من Ints، يجب كتابة طرق منفصلة:

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 implementBrief 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 methodsDefault definitionBrief 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 getindexMultidimensional and nonscalar indexing
setindex!(A, X, I...)defined in terms of scalar setindex!Multidimensional and nonscalar indexed assignment
iteratedefined in terms of scalar getindexIteration
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 indicesDefault definitionBrief 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 implementBrief 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 methodsDefault definitionBrief 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 implementBrief 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 والفهرسة. بشكل افتراضي، هذه هي دالة الهوية لجميع AbstractArrays و Numbers - فهي تدعم بالفعل 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 implementDefault definitionBrief 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 implementDefault definitionBrief description
round(x::ObjType, r::RoundingMode)noneRound 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)