بحلول هذه المرحلة من المنهج، تكون قد رأيت الكثير من تعريفات الدوال. وعادةً ما عُرضت هذه التعريفات دون تعليق يُذكر على المخرجات في REPL:
julia> f(x) = x^2
f (generic function with 1 method)
لماذا "generic function with 1 method"؟
لنفترض أننا نريد أن نكون أكثر تحديدًا بشأن أنواع الوسائط.
julia> f(x::Integer) = x^2
f (generic function with 2 methods)
julia> f(x::Real) = x^2
f (generic function with 3 methods)
أو يمكننا تغيير عدد الوسائط:
julia> f(x, y) = x * y
f (generic function with 4 methods)
يبدو أن التعريفات الجديدة للدالة نفسها تُسمّى «طرقًا». وتُضاف الطرق إلى قائمة بدلًا من أن تستبدل التعريف القديم.
يمكننا سرد الطرق بسهولة:
julia> methods(f) # output edited for brevity
# 4 methods for generic function "f" from Main:
[1] f(x, y)
[2] f(x::Integer)
[3] f(x::Real)
[4] f(x)
إذا استدعينا الدالة f، تعرف Julia بطريقة ما أي طريقة ينبغي استخدامها:
julia> f(2.5) # Float64
6.25
julia> f(2) # Int64
4
julia> f(2, 3) # Int64, Int64
6
وفقًا للدليل، الدالة كائن يربط صفًّا من الوسائط بقيمة إرجاع.
نفكّر عادةً في وسائط الدالة في f(2, 3) على أنها عنصران منفصلان، لكن المترجم يراها صفًّا واحدًا. وهذا صحيح حتى مع وسيط واحد (وإن كنا نحتاج إلى إضافة فاصلة لتوضيح ذلك):
julia> typeof( (2, 3) )
Tuple{Int64, Int64}
julia> typeof( (2,) )
Tuple{Int64}
إذا نظرنا إلى الدالة كصندوق أسود، يمكننا القول إن صفًّا يدخل وتخرج منه قيمة إرجاع. وبشكل أكثر رسمية، الصف يُربط بقيمة إرجاع.
يُشار إلى هذا عادةً باسم «تعدد الأشكال».
كان هذا موضوعًا كبيرًا في علوم الحاسوب على مدى عدة عقود، واعتُمدت مقاربات مختلفة.
مقاربة Julia غير معتادة، وإن لم تكن فريدة.
+ دالة لها 161 طريقة حاليًا).... قادرًا على تجاوز ذلك جزئيًا).dispatched) مع الوسائط المعطاة، وتُرجع نتيجة.MethodError.لاحظ أن Julia ذات أنواع ديناميكية، لذا يحدث إرسال الطرق في وقت التشغيل.
كان في القسم السابق كلام كثير، فلنترجم تلك الأفكار إلى كود. وللتبسيط، للدوال أدناه وسيط واحد فقط، لكن ليس غريبًا أن نجد دوال بعشرات الوسائط (كما في حزم الرسم البياني أو تعلّم الآلة).
ابدأ بتعريف بعض الأنواع المخصصة لتمثيل أشكال هندسية.
julia> abstract type Shape end
julia> struct Circle <: Shape
radius::Float64
end
julia> struct Square <: Shape
side::Float64
end
julia> struct Rectangle <: Shape
length::Float64
width::Float64
end
بعد ذلك يمكننا تعريف طرق لحساب مساحة كل شكل. تأخذ كل طريقة وسيطًا واحدًا بنوع ملموس مختلف يعرّفه المستخدم في كل حالة.
julia> area(c::Circle) = π * c.radius^2
area (generic function with 1 method)
julia> area(s::Square) = s.side^2
area (generic function with 2 methods)
julia> area(r::Rectangle) = r.length * r.width
area (generic function with 3 methods)
الآن يمكننا إنشاء بعض الأشكال وحساب مساحاتها، معتمدين على آلية الإرسال في Julia لاستخدام الصيغة الصحيحة لكل شكل.
julia> circle = Circle(2.3)
Circle(2.3)
julia> area(circle) # call signature Tuple{Circle}
16.619025137490002
julia> square = Square(1.6)
Square(1.6)
julia> area(square) # call signature Tuple{Square}
2.5600000000000005
julia> rectangle = Rectangle(1.4, 2.1)
Rectangle(1.4, 2.1)
julia> area(rectangle) # call signature Tuple{Rectangle}
2.94
يمكن أن تتلقى تواقيع الطرق قيمًا نوعية صورية، بالطريقة نفسها تقريبًا التي تتلقاها الأنواع ذات المعاملات النوعية.
الصياغة مميزة، إذ تتطلب جملة where:
julia> same_type(v::Vector{T}, x::T) where {T} = true
same_type (generic function with 1 method)
julia> same_type([1, 2], 3)
true
julia> same_type([1, 2], "three")
ERROR: MethodError: no method matching same_type(::Vector{Int64}, ::String)
يتطلب المثال أعلاه أن يستخدم الوسيطان النوع نفسه T، أيًّا كان.
يمكننا أن نكون أكثر تحديدًا بوضع قيد على T:
julia> same_type(v::Vector{T}, x::T) where {T<:Number} = true
same_type (generic function with 2 methods)
مع هذه الطريقة، لا تُرجع true إلا الأنواع العددية المتطابقة.
ويمكن استخدام عدة متغيرات صورية، مفصولة بفواصل داخل جملة where.
julia> myfunc(a::S, b::T) where {S, T} = true
myfunc (generic function with 1 method)
الإرسال المتعدد مرن جدًا وعالي الأداء في Julia، لذا يُستخدم على نطاق واسع.
وكما يوحي مثال عامل + ذي الـ161 طريقة، يسعى مبرمجو Julia إلى تعريف طرق بأنواع محددة بدقة.
جمع عددين صحيحين يختلف كثيرًا عن جمع عددين عشريين على مستوى الكود الثنائي، كما يضيف جمع عدد صحيح إلى عدد عشري تعقيدًا إضافيًا يتمثل في ترقية الأنواع.
وكلما كان النوع أكثر تحديدًا، زاد ما يستطيع المترجم القيام به من تحسينات: وهذا ذو أهمية هامشية في تمرين صغير على Exercism، لكنه حيوي للأداء في المحاكيات العددية الكبيرة التي صُمِّمت Julia من أجلها.
إذا كنت تعرف المدخلات التي ستتلقاها طريقة ما، فأخبر المترجم بذلك!
في هذا التمرين، ستُنفّذ محاكاةً للقاءات. سيمنحك هذا إلمامًا بأساسيات الإرسال المتعدد، وهو النموذج الرئيسي في Julia.
For this exercise, all function definitions should be a single-line declaration with no if/else logic.
بشكل عام، يتضمّن اللقاء كيانًا a يلتقي كيانًا آخر b ويتفاعل معه.
encounter(a, b) = "$(name(a)) meets $(name(b)) and $(meets(a, b))."
في البداية، سنحاكي ما يحدث عندما تلتقي القطط والكلاب.
أولًا، أضف نوعًا مجرّدًا Pet.
ثم عرّف النوعين Cat و Dog كنوعين فرعيين من Pet.
لكلٍّ منهما حقل واحد هو name.
نفّذ دالة name() تُرجع اسم الحيوان الأليف.
julia> fido = Dog("Fido")
Dog("Fido")
julia> name(fido)
"Fido"
نفّذ طرق meets() للقاءات التالية:
julia> ginger = Cat("Ginger")
Cat("Ginger")
julia> meets(ginger, fido)
"hisses"
نفّذ دالة encounter() بطريقة واحدة، لتُبلّغ عمّا يحدث عندما يلتقي كيانان لهما اسمان.
julia> encounter(ginger, fido)
"Ginger meets Fido and hisses."
ماذا يحدث إذا صادفوا حيوانًا أليفًا مختلفًا أثناء نزهتهم، مثل حصان؟
julia> bella = Horse("Bella")
Horse("Bella")
julia> encounter(fido, bella)
"Fido meets Bella and is cautious."
هناك أشياء أخرى كثيرة قد تصادفها الحيوانات الأليفة وليست حيوانات أليفة: السيارات والبشر والنباتات والكوارث الطبيعية والكويكبات… فماذا يحدث حينها؟
julia> colnago = Bicycle("Colnago")
Bicycle("Colnago")
julia> encounter(ginger, colnago)
"Ginger meets Colnago and runs away."
هناك لقاءات أخرى كثيرة قد تحدث في محاكاتنا. سيارة تلتقي بكلب، وإلكترونان يلتقيان ويتفاعلان…
من المستحيل تغطية كل اللقاءات مسبقًا، لذلك سننفّذ ردًّا احتياطيًا عامًّا.
julia> γ1 = Photon("γ1")
Photon("γ1")
julia> γ2 = Photon("γ2")
Photon("γ2")
julia> encounter(γ1, γ2)
"γ1 meets γ2 and nothing happens."
# This is true, photons just pass through each other.
سجّل في Exercism لتتعلّم وتتقن Julia عبر 35 مفهومًا128 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.