সিলেবাসের এই পর্যায়ে এসে আপনি ইতিমধ্যে অনেক ফাংশন ডেফিনিশন দেখে ফেলেছেন। সাধারণত এগুলো 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-এর একটি ছোট্ট অনুশীলনীতে এর গুরুত্ব প্রান্তিক, কিন্তু জুলিয়া যে বড় বড় সংখ্যাসূচক সিমুলেশনের জন্য তৈরি, সেখানে পারফরম্যান্সের জন্য এটি অপরিহার্য।
আপনি যদি জানেন একটি মেথড কী কী ইনপুট পাবে, তবে কম্পাইলারকে জানিয়ে দিন!
এই অনুশীলনীতে আপনি সাক্ষাতের একটি সিমুলেশন তৈরি করবেন। এতে আপনি 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টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।