تا اینجای برنامهی درسی، تعریفهای زیادی از تابع دیدهاید. معمولاً این تعریفها بدون توضیح چندانی دربارهی خروجی در 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> 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}
اگر تابع را مثل یک جعبهی سیاه در نظر بگیریم، میتوان گفت که یک تاپل وارد میشود و یک مقدار بازگشتی بیرون میآید. به شکل رسمیتر، تاپل به یک مقدار بازگشتی نگاشت میشود.
معمولاً به این «چندریختی» میگویند.
این موضوع چندین دهه است که یکی از مباحث مهم علوم کامپیوتر بوده و رویکردهای مختلفی برای آن اتخاذ شده است.
رویکرد جولیا غیرمعمول است، هرچند بینظیر نیست.
+ در حال حاضر تابعی است با ۱۶۱ روش).... میتواند تا حدی از این محدودیت عبور کند).dispatched میشود و نتیجهای برمیگرداند.MethodError صادر میشود.توجه کنید که جولیا نوعبندی پویا دارد، بنابراین اعزام روش در زمان اجرا رخ میدهد.
بخش قبلی پر از حرف بود، پس بیایید این ایدهها را به کد تبدیل کنیم. برای سادگی، توابع زیر فقط یک آرگومان دارند، اما پیدا کردن توابعی با دهها آرگومان غیرمعمول نیست (مثلاً در بستههای رسم نمودار یا یادگیری ماشین).
برای شروع، چند نوع سفارشی تعریف کنید که شکلهای هندسی را نمایش میدهند.
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> 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)
اعزام چندگانه در جولیا بسیار انعطافپذیر و بسیار کارآمد است، به همین دلیل بسیار گسترده استفاده میشود.
همانطور که مثال ۱۶۱ روش برای + نشان میدهد، برنامهنویسان جولیا تلاش میکنند روشهایی با نوعهای کاملاً مشخص تعریف کنند.
جمع کردن دو عدد صحیح در سطح بایتکد با جمع کردن دو عدد اعشاری بسیار متفاوت است و جمع کردن یک عدد صحیح با یک عدد اعشاری، پیچیدگی دیگری به نام ارتقای نوع را اضافه میکند.
هرچه نوع مشخصتر باشد، کامپایلر میتواند بهینهسازی بیشتری انجام دهد: در یک تمرین کوچک در Exercism اهمیت چندانی ندارد، اما برای کارایی در شبیهسازیهای عددی بزرگی که جولیا برای آنها طراحی شده حیاتی است.
اگر میدانید یک روش چه ورودیهایی دریافت میکند، لطفاً به کامپایلر بگویید!
در این تمرین، شبیهسازیای از برخوردها را پیادهسازی میکنید. این کار شما را با مبانی multiple dispatch، پارادایم اصلی 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.