More about types
إذا كنت قد استخدمت جوليا لفترة، فأنت تفهم الدور الأساسي الذي تلعبه الأنواع. هنا نحاول التعمق في التفاصيل، مع التركيز بشكل خاص على Parametric Types.
Types and sets (and Any
and Union{}
/Bottom
)
من المحتمل أن يكون من الأسهل تصور نظام أنواع جوليا من حيث المجموعات. بينما تتلاعب البرامج بالقيم الفردية، تشير النوع إلى مجموعة من القيم. هذا ليس هو نفس الشيء مثل المجموعة؛ على سبيل المثال، Set
من القيم هو في حد ذاته قيمة Set
واحدة. بدلاً من ذلك، يصف النوع مجموعة من القيم المحتملة، معبراً عن عدم اليقين بشأن القيمة التي لدينا.
نوع خرساني T
يصف مجموعة القيم التي يتم إرجاع علامةها المباشرة، كما هو موضح بواسطة دالة typeof
، هي T
. نوع مجرد يصف مجموعة من القيم التي قد تكون أكبر.
Any
يصف الكون بأكمله من القيم الممكنة. Integer
هو مجموعة فرعية من Any
التي تشمل Int
، Int8
، وأنواع ملموسة أخرى. داخليًا، تستخدم جوليا أيضًا نوعًا آخر معروفًا باسم Bottom
، والذي يمكن أيضًا كتابته كـ Union{}
. هذا يتوافق مع المجموعة الفارغة.
تدعم أنواع جوليا العمليات القياسية في نظرية المجموعات: يمكنك أن تسأل ما إذا كانت T1
هي "مجموعة فرعية" (نوع فرعي) من T2
باستخدام T1 <: T2
. وبالمثل، يمكنك تقاطع نوعين باستخدام typeintersect
، وأخذ اتحادهم باستخدام Union
، وحساب نوع يحتوي على اتحادهم باستخدام typejoin
:
julia> typeintersect(Int, Float64)
Union{}
julia> Union{Int, Float64}
Union{Float64, Int64}
julia> typejoin(Int, Float64)
Real
julia> typeintersect(Signed, Union{UInt8, Int8})
Int8
julia> Union{Signed, Union{UInt8, Int8}}
Union{UInt8, Signed}
julia> typejoin(Signed, Union{UInt8, Int8})
Integer
julia> typeintersect(Tuple{Integer, Float64}, Tuple{Int, Real})
Tuple{Int64, Float64}
julia> Union{Tuple{Integer, Float64}, Tuple{Int, Real}}
Union{Tuple{Int64, Real}, Tuple{Integer, Float64}}
julia> typejoin(Tuple{Integer, Float64}, Tuple{Int, Real})
Tuple{Integer, Real}
بينما قد تبدو هذه العمليات مجردة، إلا أنها تكمن في قلب جوليا. على سبيل المثال، يتم تنفيذ توجيه الأساليب من خلال المرور عبر العناصر في قائمة الأساليب حتى الوصول إلى واحدة يكون فيها نوع مجموعة الوسائط فرعًا من توقيع الأسلوب. لكي تعمل هذه الخوارزمية، من المهم أن تكون الأساليب مرتبة حسب خصوصيتها، وأن تبدأ عملية البحث بأكثر الأساليب تحديدًا. وبالتالي، تقوم جوليا أيضًا بتنفيذ ترتيب جزئي على الأنواع؛ يتم تحقيق ذلك من خلال وظيفة مشابهة لـ <:
، ولكن مع اختلافات سيتم مناقشتها أدناه.
UnionAll types
يمكن لنظام أنواع جوليا أيضًا التعبير عن اتحاد متكرر للأنواع: اتحاد للأنواع على جميع قيم متغير ما. هذا ضروري لوصف الأنواع المعاملية حيث لا تُعرف قيم بعض المعاملات.
على سبيل المثال، Array
يحتوي على معلمتين كما في Array{Int,2}
. إذا لم نكن نعرف نوع العنصر، يمكننا كتابة Array{T,2} where T
، وهو اتحاد Array{T,2}
لجميع قيم T
: Union{Array{Int8,2}, Array{Int16,2}, ...}
.
يمثل هذا النوع كائن UnionAll
، الذي يحتوي على متغير (T
في هذا المثال، من نوع TypeVar
)، ونوع مغلف (Array{T,2}
في هذا المثال).
اعتبر الطرق التالية:
f1(A::Array) = 1
f2(A::Array{Int}) = 2
f3(A::Array{T}) where {T<:Any} = 3
f4(A::Array{Any}) = 4
التوقيع - كما هو موصوف في Function calls - لـ f3
هو نوع UnionAll
يلتف حول نوع التوابل: Tuple{typeof(f3), Array{T}} where T
. يمكن استدعاء جميع الدوال باستثناء f4
مع a = [1,2]
؛ يمكن استدعاء جميع الدوال باستثناء f2
مع b = Any[1,2]
.
دعونا نلقي نظرة على هذه الأنواع عن كثب:
julia> dump(Array)
UnionAll
var: TypeVar
name: Symbol T
lb: Union{}
ub: Any
body: UnionAll
var: TypeVar
name: Symbol N
lb: Union{}
ub: Any
body: Array{T, N} <: DenseArray{T, N}
ref::MemoryRef{T}
size::NTuple{N, Int64}
هذا يشير إلى أن Array
في الواقع يسمي نوع UnionAll
. هناك نوع UnionAll
واحد لكل معلمة، متداخلة. الصيغة Array{Int,2}
تعادل Array{Int}{2}
؛ داخليًا، يتم إنشاء كل UnionAll
بقيمة متغيرة معينة، واحدة في كل مرة، من الخارج إلى الداخل. هذا يعطي معنى طبيعي لترك معلمات النوع اللاحقة؛ Array{Int}
يعطي نوعًا يعادل Array{Int,N} حيث N
.
TypeVar
ليست نوعًا بحد ذاتها، بل يجب اعتبارها جزءًا من بنية نوع UnionAll
. تحتوي متغيرات النوع على حدود دنيا وعليا لقيمها (في الحقول lb
و ub
). الرمز name
هو تجميلي بحت. داخليًا، يتم مقارنة TypeVar
s حسب العنوان، لذا يتم تعريفها كأنواع قابلة للتغيير لضمان إمكانية تمييز "متغيرات النوع المختلفة". ومع ذلك، يجب عدم تغييرها وفقًا للعرف.
يمكن للمرء إنشاء TypeVar
s يدويًا:
julia> TypeVar(:V, Signed, Real)
Signed<:V<:Real
هناك إصدارات مريحة تتيح لك حذف أي من هذه المعاملات باستثناء رمز name
.
يتم تقليل الصيغة Array{T} where T<:Integer
إلى
let T = TypeVar(:T,Integer)
UnionAll(T, Array{T})
end
لذا نادرًا ما يكون من الضروري إنشاء TypeVar
يدويًا (في الواقع، يجب تجنب ذلك).
Free variables
مفهوم المتغير من النوع المجاني مهم للغاية في نظام الأنواع. نقول إن المتغير V
مجاني في النوع T
إذا كان T
لا يحتوي على UnionAll
الذي يقدم المتغير V
. على سبيل المثال، النوع Array{Array{V} where V<:Integer}
ليس لديه متغيرات مجانية، لكن الجزء Array{V}
بداخله يحتوي على متغير مجاني، وهو V
.
نوع يحتوي على متغيرات حرة هو، بمعنى ما، ليس نوعًا حقًا على الإطلاق. اعتبر النوع Array{Array{T}} where T
، الذي يشير إلى جميع المصفوفات المتجانسة من المصفوفات. النوع الداخلي Array{T}
، الذي يُنظر إليه بمفرده، قد يبدو أنه يشير إلى أي نوع من المصفوفات. ومع ذلك، يجب أن يكون لكل عنصر من المصفوفة الخارجية نفس نوع المصفوفة، لذا لا يمكن أن يشير Array{T}
إلى أي مصفوفة قديمة. يمكن القول إن Array{T}
"يحدث" فعليًا عدة مرات، ويجب أن يكون لـ T
نفس القيمة في كل "مرة".
لهذا السبب، فإن الدالة jl_has_free_typevars
في واجهة برمجة التطبيقات C مهمة جدًا. الأنواع التي تعيد true لها لن تعطي إجابات ذات مغزى في التوريث وغيرها من وظائف النوع.
TypeNames
النوعان التاليان Array
متساويان وظيفيًا، ومع ذلك يطبعا بشكل مختلف:
julia> TV, NV = TypeVar(:T), TypeVar(:N)
(T, N)
julia> Array
Array
julia> Array{TV, NV}
Array{T, N}
يمكن تمييز هذه من خلال فحص حقل name
من النوع، وهو كائن من نوع TypeName
:
julia> dump(Array{Int,1}.name)
TypeName
name: Symbol Array
module: Module Core
names: empty SimpleVector
wrapper: UnionAll
var: TypeVar
name: Symbol T
lb: Union{}
ub: Any
body: UnionAll
var: TypeVar
name: Symbol N
lb: Union{}
ub: Any
body: Array{T, N} <: DenseArray{T, N}
cache: SimpleVector
...
linearcache: SimpleVector
...
hash: Int64 -7900426068641098781
mt: MethodTable
name: Symbol Array
defs: Nothing nothing
cache: Nothing nothing
max_args: Int64 0
module: Module Core
: Int64 0
: Int64 0
في هذه الحالة، الحقل المعني هو wrapper
، الذي يحتفظ بإشارة إلى النوع العلوي المستخدم لإنشاء أنواع Array
جديدة.
julia> pointer_from_objref(Array)
Ptr{Cvoid} @0x00007fcc7de64850
julia> pointer_from_objref(Array.body.body.name.wrapper)
Ptr{Cvoid} @0x00007fcc7de64850
julia> pointer_from_objref(Array{TV,NV})
Ptr{Cvoid} @0x00007fcc80c4d930
julia> pointer_from_objref(Array{TV,NV}.name.wrapper)
Ptr{Cvoid} @0x00007fcc7de64850
حقل wrapper
من Array
يشير إلى نفسه، ولكن بالنسبة لـ Array{TV,NV}
فإنه يشير مرة أخرى إلى التعريف الأصلي لنوع البيانات.
ماذا عن الحقول الأخرى؟ hash
يخصص عددًا صحيحًا لكل نوع. لفحص حقل cache
، من المفيد اختيار نوع يستخدم أقل من Array. دعنا أولاً ننشئ نوعنا الخاص:
julia> struct MyType{T,N} end
julia> MyType{Int,2}
MyType{Int64, 2}
julia> MyType{Float32, 5}
MyType{Float32, 5}
عند إنشاء نوع بارامتري، يتم حفظ كل نوع ملموس في ذاكرة التخزين المؤقت للنمط (MyType.body.body.name.cache
). ومع ذلك، فإن الحالات التي تحتوي على متغيرات نوع حرة لا يتم تخزينها في الذاكرة المؤقتة.
Tuple types
تشكل أنواع التوائم حالة خاصة مثيرة للاهتمام. لكي يعمل التوزيع على إعلانات مثل x::Tuple
، يجب أن يكون النوع قادرًا على استيعاب أي توأم. دعنا نتحقق من المعلمات:
julia> Tuple
Tuple
julia> Tuple.parameters
svec(Vararg{Any})
على عكس الأنواع الأخرى، فإن أنواع التوابل متغيرة في معلماتها، لذا فإن هذا التعريف يسمح لـ Tuple
بمطابقة أي نوع من أنواع التوابل:
julia> typeintersect(Tuple, Tuple{Int,Float64})
Tuple{Int64, Float64}
julia> typeintersect(Tuple{Vararg{Any}}, Tuple{Int,Float64})
Tuple{Int64, Float64}
ومع ذلك، إذا كان نوع التوابل المتغير (Vararg
) يحتوي على متغيرات حرة، فإنه يمكن أن يصف أنواعًا مختلفة من التوابل:
julia> typeintersect(Tuple{Vararg{T} where T}, Tuple{Int,Float64})
Tuple{Int64, Float64}
julia> typeintersect(Tuple{Vararg{T}} where T, Tuple{Int,Float64})
Union{}
لاحظ أنه عندما يكون T
حراً بالنسبة لنوع Tuple
(أي أن ارتباطه من نوع UnionAll
خارج نوع Tuple
)، يجب أن تعمل قيمة واحدة فقط من T
على النوع بأكمله. لذلك، لا يتطابق التوابل غير المتجانسة.
أخيرًا، من الجدير بالذكر أن Tuple{}
مميز:
julia> Tuple{}
Tuple{}
julia> Tuple{}.parameters
svec()
julia> typeintersect(Tuple{}, Tuple{Int})
Union{}
ما هو نوع التوابل "الرئيسي"؟
julia> pointer_from_objref(Tuple)
Ptr{Cvoid} @0x00007f5998a04370
julia> pointer_from_objref(Tuple{})
Ptr{Cvoid} @0x00007f5998a570d0
julia> pointer_from_objref(Tuple.name.wrapper)
Ptr{Cvoid} @0x00007f5998a04370
julia> pointer_from_objref(Tuple{}.name.wrapper)
Ptr{Cvoid} @0x00007f5998a04370
لذا Tuple == Tuple{Vararg{Any}}
هو بالفعل النوع الأساسي.
Diagonal types
اعتبر النوع Tuple{T,T} where T
. ستبدو طريقة تحمل هذا التوقيع كما يلي:
f(x::T, y::T) where {T} = ...
وفقًا للتفسير المعتاد لنوع UnionAll
، فإن هذا T
يتراوح بين جميع الأنواع، بما في ذلك Any
، لذا يجب أن يكون هذا النوع مكافئًا لـ Tuple{Any,Any}
. ومع ذلك، فإن هذا التفسير يسبب بعض المشاكل العملية.
أولاً، يجب أن تكون قيمة T
متاحة داخل تعريف الطريقة. بالنسبة لاستدعاء مثل f(1, 1.0)
، ليس من الواضح ما يجب أن تكون عليه T
. يمكن أن تكون Union{Int,Float64}
، أو ربما Real
. بشكل بديهي، نتوقع أن تعني الإعلانات x::T
أن T === typeof(x)
. للتأكد من أن هذا الثبات قائم، نحتاج إلى typeof(x) === typeof(y) === T
في هذه الطريقة. وهذا يعني أن الطريقة يجب أن تُستدعى فقط للوسائط من نفس النوع بالضبط.
يتضح أن القدرة على التوزيع بناءً على ما إذا كانت قيمتان لهما نفس النوع مفيدة جدًا (يتم استخدام ذلك من قبل نظام الترويج على سبيل المثال)، لذا لدينا عدة أسباب لرغبتنا في تفسير مختلف لـ Tuple{T,T} where T
. لجعل هذا يعمل، نضيف القاعدة التالية إلى تصنيف الأنواع: إذا حدثت متغير أكثر من مرة في موضع متزايد، فإنه يُقيد ليشمل فقط الأنواع المحددة. ("الموضع المتزايد" يعني أن أنواع Tuple
و Union
فقط تحدث بين حدوث متغير ونوع UnionAll
الذي يقدمها.) تُسمى هذه المتغيرات "متغيرات قطرية" أو "متغيرات محددة".
لذا على سبيل المثال، Tuple{T,T} where T
يمكن أن يُعتبر كـ Union{Tuple{Int8,Int8}, Tuple{Int16,Int16}, ...}
، حيث يتراوح T
على جميع الأنواع المحددة. وهذا يؤدي إلى بعض النتائج المثيرة للاهتمام في التوريث. على سبيل المثال، Tuple{Real,Real}
ليس نوعًا فرعيًا من Tuple{T,T} where T
، لأنه يتضمن بعض الأنواع مثل Tuple{Int8,Int16}
حيث يكون للعناصر الاثنين أنواع مختلفة. Tuple{Real,Real}
و Tuple{T,T} where T
لهما تقاطع غير تافه وهو Tuple{T,T} where T<:Real
. ومع ذلك، Tuple{Real}
هو نوع فرعي من Tuple{T} where T
، لأنه في هذه الحالة يحدث T
مرة واحدة فقط وبالتالي لا يكون مائلًا.
بعد ذلك، اعتبر توقيعًا مثل ما يلي:
f(a::Array{T}, x::T, y::T) where {T} = ...
في هذه الحالة، يحدث T
في موضع ثابت داخل Array{T}
. هذا يعني أن أي نوع من المصفوفات يتم تمريره يحدد بشكل غير ملتبس قيمة T
- نقول إن T
لديه قيود تساوي عليه. لذلك، في هذه الحالة، فإن قاعدة القطر ليست ضرورية حقًا، حيث تحدد المصفوفة T
ويمكننا بعد ذلك السماح لـ x
و y
بأن يكونا من أي نوع فرعي من T
. لذا، فإن المتغيرات التي تحدث في موضع ثابت لا تعتبر أبدًا قطرية. هذا الاختيار في السلوك مثير للجدل قليلاً - يشعر البعض أن هذا التعريف يجب أن يُكتب كـ
f(a::Array{T}, x::S, y::S) where {T, S<:T} = ...
لتوضيح ما إذا كان يجب أن يكون لـ x
و y
نفس النوع. في هذه النسخة من التوقيع سيكونان كذلك، أو يمكننا إدخال متغير ثالث لنوع y
إذا كان يمكن أن يكون لـ x
و y
أنواع مختلفة.
التعقيد التالي هو تفاعل النقابات والمتغيرات القطرية، على سبيل المثال
f(x::Union{Nothing,T}, y::T) where {T} = ...
اعتبر ما تعنيه هذه الإعلانات. y
له نوع T
. ثم يمكن أن يكون x
إما من نفس النوع T
، أو أن يكون من النوع Nothing
. لذا يجب أن تتطابق جميع المكالمات التالية:
f(1, 1)
f("", "")
f(2.0, 2.0)
f(nothing, 1)
f(nothing, "")
f(nothing, 2.0)
هذه الأمثلة تخبرنا بشيء: عندما يكون x
هو nothing::Nothing
، لا توجد قيود إضافية على y
. إنه كما لو كانت توقيع الطريقة يحتوي على y::Any
. في الواقع، لدينا المعادلة النوعية التالية:
(Tuple{Union{Nothing,T},T} where T) == Union{Tuple{Nothing,Any}, Tuple{T,T} where T}
القاعدة العامة هي: المتغير الملموس في موضع متغير يتصرف كما لو لم يكن ملموسًا إذا كانت خوارزمية التوريث تستخدمه مرة واحدة فقط. عندما يكون x
من النوع Nothing
، لا نحتاج إلى استخدام T
في Union{Nothing,T}
؛ نحن نستخدمه فقط في الفتحة الثانية. ينشأ هذا بشكل طبيعي من الملاحظة أنه في Tuple{T} where T
، فإن تقييد T
إلى أنواع ملموسة لا يحدث فرقًا؛ النوع يساوي Tuple{Any}
في كلتا الحالتين.
ومع ذلك، فإن الظهور في موضع ثابت يستبعد متغيرًا من كونه ملموسًا سواء تم استخدام ذلك الظهور للمتغير أم لا. خلاف ذلك، يمكن أن تتصرف الأنواع بشكل مختلف اعتمادًا على الأنواع الأخرى التي تتم مقارنتها بها، مما يجعل التوريث غير متعدٍ. على سبيل المثال، اعتبر
Tuple{Int,Int8,Vector{Integer}} <: Tuple{T,T,Vector{Union{Integer,T}}} where T
إذا تم تجاهل T
داخل Union
، فإن T
يكون ملموسًا والإجابة هي "خطأ" لأن النوعين الأولين ليسا متطابقين. ولكن اعتبر بدلاً من ذلك
Tuple{Int,Int8,Vector{Any}} <: Tuple{T,T,Vector{Union{Integer,T}}} where T
الآن لا يمكننا تجاهل T
في Union
(يجب أن يكون لدينا T == Any
)، لذا فإن T
ليس محددًا والإجابة هي "صحيح". هذا سيجعل تحديد T
يعتمد على النوع الآخر، وهو أمر غير مقبول لأن النوع يجب أن يكون له معنى واضح بمفرده. لذلك، يُعتبر ظهور T
داخل Vector
في كلا الحالتين.
Subtyping diagonal variables
خوارزمية التفرع للمتغيرات القطرية تحتوي على مكونين: (1) تحديد حدوث المتغيرات، و(2) ضمان أن المتغيرات القطرية تتراوح فقط على أنواع ملموسة.
تتمثل المهمة الأولى في الحفاظ على العدادات occurs_inv
و occurs_cov
(في src/subtype.c
) لكل متغير في البيئة، وتتبع عدد الحدوث الثابت والحادث المتغير، على التوالي. يكون المتغير قطريًا عندما يكون occurs_inv == 0 && occurs_cov > 1
.
المهمة الثانية تُنجز من خلال فرض شرط على الحد الأدنى لمتغير. بينما يعمل خوارزمية التفرع، تضيق الحدود لكل متغير (رفع الحدود الدنيا وخفض الحدود العليا) لتتبع نطاق قيم المتغيرات التي ستظل فيها علاقة التفرع قائمة. عندما ننتهي من تقييم جسم نوع UnionAll
الذي يكون متغيره قطريًا، ننظر إلى القيم النهائية للحدود. نظرًا لأن المتغير يجب أن يكون ملموسًا، يحدث تناقض إذا لم يكن من الممكن أن يكون حده الأدنى نوعًا فرعيًا من نوع ملموس. على سبيل المثال، لا يمكن أن يكون نوع مجرد مثل AbstractArray
نوعًا فرعيًا من نوع ملموس، ولكن يمكن أن يكون نوع ملموس مثل Int
، ويمكن أن يكون النوع الفارغ Bottom
كذلك. إذا فشل حد أدنى في هذا الاختبار، تتوقف الخوارزمية مع الإجابة false
.
على سبيل المثال، في المشكلة Tuple{Int,String} <: Tuple{T,T} where T
، نستنتج أن هذا سيكون صحيحًا إذا كان T
نوعًا فوقيًا لـ Union{Int,String}
. ومع ذلك، فإن Union{Int,String}
هو نوع تجريدي، لذا فإن العلاقة لا تنطبق.
يتم إجراء اختبار التحديد هذا بواسطة الدالة is_leaf_bound
. لاحظ أن هذا الاختبار يختلف قليلاً عن jl_is_leaf_type
، حيث إنه يعيد أيضًا true
لـ Bottom
. حاليًا، هذه الدالة هي تجريبية، ولا تلتقط جميع الأنواع الملموسة الممكنة. تكمن الصعوبة في أن ما إذا كان الحد الأدنى ملموسًا قد يعتمد على قيم حدود متغيرات النوع الأخرى. على سبيل المثال، Vector{T}
يعادل النوع الملموس Vector{Int}
فقط إذا كانت كل من الحدود العليا والسفلى لـ T
تساوي Int
. لم نعمل بعد على خوارزمية كاملة لذلك.
Introduction to the internal machinery
توجد معظم العمليات المتعلقة بالأنواع في الملفات jltypes.c
و subtype.c
. طريقة جيدة للبدء هي مشاهدة التحتية أثناء العمل. قم ببناء جوليا باستخدام make debug
وابدأ جوليا داخل مصحح الأخطاء. gdb debugging tips يحتوي على بعض النصائح التي قد تكون مفيدة.
لأن كود التفرع الفرعي يُستخدم بشكل مكثف في REPL نفسه - وبالتالي يتم تفعيل نقاط التوقف في هذا الكود بشكل متكرر - سيكون من الأسهل إذا قمت بعمل التعريف التالي:
julia> function mysubtype(a,b)
ccall(:jl_breakpoint, Cvoid, (Any,), nothing)
a <: b
end
ثم قم بتعيين نقطة توقف في jl_breakpoint
. بمجرد أن يتم تفعيل هذه النقطة، يمكنك تعيين نقاط توقف في دوال أخرى.
كتمهيد، جرب ما يلي:
mysubtype(Tuple{Int, Float64}, Tuple{Integer, Real})
يمكننا جعلها أكثر إثارة من خلال تجربة حالة أكثر تعقيدًا:
mysubtype(Tuple{Array{Int,2}, Int8}, Tuple{Array{T}, T} where T)
Subtyping and method sorting
تُستخدم دوال type_morespecific
لفرض ترتيب جزئي على الدوال في جداول الطرق (من الأكثر تحديدًا إلى الأقل تحديدًا). التحديد صارم؛ إذا كانت a
أكثر تحديدًا من b
، فإن a
لا تساوي b
و b
ليست أكثر تحديدًا من a
.
إذا كان a
هو نوع فرعي صارم من b
، فإنه يعتبر تلقائيًا أكثر تحديدًا. من هناك، يستخدم type_morespecific
بعض القواعد الأقل رسمية. على سبيل المثال، subtype
حساس لعدد المعاملات، لكن type_morespecific
قد لا يكون كذلك. على وجه الخصوص، Tuple{Int,AbstractFloat}
أكثر تحديدًا من Tuple{Integer}
، على الرغم من أنه ليس نوعًا فرعيًا. (من Tuple{Int,AbstractFloat}
و Tuple{Integer,Float64}
، لا يعتبر أي منهما أكثر تحديدًا من الآخر.) وبالمثل، Tuple{Int,Vararg{Int}}
ليس نوعًا فرعيًا من Tuple{Integer}
، لكنه يعتبر أكثر تحديدًا. ومع ذلك، يحصل morespecific
على ميزة للطول: على وجه الخصوص، Tuple{Int,Int}
أكثر تحديدًا من Tuple{Int,Vararg{Int}}
.
إذا كنت تقوم بتصحيح كيفية ترتيب الطرق، فقد يكون من الملائم تعريف الدالة:
type_morespecific(a, b) = ccall(:jl_type_morespecific, Cint, (Any,Any), a, b)
الذي يتيح لك اختبار ما إذا كان نوع التوابل a
أكثر تحديدًا من نوع التوابل b
.