পলিমরফিজম — প্যারামেট্রিক, অ্যাড-হক ও সাবটাইপ
এই পাঠে যা শিখবেন
- তিন ধরনের পলিমরফিজমের সুনির্দিষ্ট সংজ্ঞা এবং একে অপরের থেকে তাদের পার্থক্য
- প্যারামেট্রিক পলিমরফিজম — একটি সত্যিকারের জেনেরিক ফাংশনের বাস্তবায়ন
- অ্যাড-হক পলিমরফিজম — একটি টাইপ-ডিসপ্যাচড ফাংশনের বাস্তবায়ন
- সাবটাইপ পলিমরফিজম — Shape/Rectangle/Circle হায়ারার্কি দিয়ে Liskov Substitution Principle
১ · একটি নাম, তিনটি ভিন্ন ধারণা
L07-এ OOP পড়ার সময় "পলিমরফিজম" শব্দটি সংক্ষেপে উল্লেখ করা হয়েছিল — একই মেথড কল ভিন্ন ভিন্ন আচরণ করে, অবজেক্টের রানটাইম টাইপের উপর নির্ভর করে। কিন্তু টাইপ থিওরিতে "পলিমরফিজম" শব্দটি আসলে তিনটি সম্পূর্ণ ভিন্ন ধারণাকে বোঝায় — এই পাঠ প্রতিটিকে আলাদাভাবে, স্পষ্টভাবে সংজ্ঞায়িত করবে, যা প্রায়ই বিভ্রান্তির একটি সাধারণ উৎস।
২ · প্যারামেট্রিক পলিমরফিজম
প্যারামেট্রিক পলিমরফিজমParametric Polymorphismএকটিই কোড, কোনো টাইপ-নির্দিষ্ট পরিবর্তন ছাড়াই, যেকোনো টাইপের জন্য অভিন্নভাবে কাজ করে।-এ
একটিই কোড, লেখার সময় নির্দিষ্ট কোনো টাইপ না জেনেই, যেকোনো টাইপের জন্য অভিন্নভাবে কাজ
করে — উদাহরণ: একটি জেনেরিক identity(x) = x ফাংশন, বা একটি জেনেরিক List<T>
কন্টেইনার — একই কোড, কোনো পরিবর্তন ছাড়াই, List<int> ও List<string>
উভয়ের জন্যই কাজ করে। এটি সরাসরি L34-এর টাইপ ইনফারেন্সের সাথে সম্পর্কিত — টাইপ ইনফারেন্স প্রায়ই স্বয়ংক্রিয়ভাবে
সঠিক প্যারামেট্রিক ইনস্ট্যান্সিয়েশন বের করে নেয়।
৩ · অ্যাড-হক পলিমরফিজম (ওভারলোডিং)
অ্যাড-হক পলিমরফিজমAd-hoc Polymorphism / Overloadingএকই ফাংশন/অপারেটর নামের একাধিক, সত্যিকারভাবে ভিন্ন বাস্তবায়ন থাকে, নির্দিষ্ট আর্গুমেন্ট টাইপ অনুযায়ী নির্বাচিত হয়।-এ
একই ফাংশন/অপারেটর নামের একাধিক, সত্যিকারভাবে ভিন্ন বাস্তবায়ন থাকে,
দেওয়া নির্দিষ্ট আর্গুমেন্ট টাইপ অনুযায়ী নির্বাচিত হয় — উদাহরণ: + অপারেটর দুটো int-এর জন্য
যোগ বোঝায়, কিন্তু দুটো string-এর জন্য কনক্যাটেনেশন বোঝায় (সরাসরি L30-এর টাইপ-নির্ভর অপারেটর আচরণের কলব্যাক)।
প্যারামেট্রিক পলিমরফিজমের বিপরীতে — এখানে একটি অভিন্ন বাস্তবায়ন নেই; সত্যিকারের আলাদা
বাস্তবায়ন আছে, টাইপ দিয়ে ডিসপ্যাচ করা হয়।
৪ · সাবটাইপ পলিমরফিজম
সাবটাইপ পলিমরফিজমSubtype Polymorphismএকটি সাবটাইপের ভ্যালু যেকোনো জায়গায় ব্যবহারযোগ্য যেখানে তার সুপারটাইপের ভ্যালু প্রত্যাশিত।
(L07-এর OOP ইনহেরিটেন্স-ভিত্তিক পলিমরফিজমের পূর্ণাঙ্গ ফরমালাইজেশন) — একটি সাবটাইপের
ভ্যালু যেখানেই তার সুপারটাইপের ভ্যালু প্রত্যাশিত, সেখানেই ব্যবহারযোগ্য — উদাহরণ:
Shape-এর সাবটাইপ হওয়ায় একটি Circle অবজেক্ট যেকোনো জায়গায় ব্যবহার করা যায় যেখানে
একটি Shape প্রত্যাশিত। Liskov Substitution Principle (নাম উল্লেখযোগ্য): একটি
সাবটাইপ তার সুপারটাইপের একটি ড্রপ-ইন প্রতিস্থাপন হিসেবে ব্যবহারযোগ্য হওয়া উচিত, সুপারটাইপের বিপরীতে লেখা
কোডের সঠিকতা না ভেঙে।
একটিই বাস্তবায়ন — কোনো টাইপ-নির্দিষ্ট শাখা নেই — যেকোনো টাইপে কাজ করে।
একাধিক, সত্যিকারভাবে আলাদা বাস্তবায়ন — টাইপ দিয়ে ডিসপ্যাচড।
একটি বাস্তবায়ন প্রতি সাবটাইপে (ওভাররাইড করা) — কিন্তু একটি অভিন্ন হায়ারার্কি জুড়ে বিনিময়যোগ্য (substitutable)।
৫ · কোড দিয়ে যাচাই — তিনটিই পাশাপাশি
নিচের কোড সেলে তিন ধরনের পলিমরফিজমই স্বতন্ত্রভাবে বাস্তবায়ন করা হয়েছে — সাবটাইপ অংশে L07-এর
Shape/Rectangle/Circle হায়ারার্কিই পুনরায় ব্যবহার করা হচ্ছে,
যাতে ধারাবাহিকতা বজায় থাকে।
import math
# ============ (১) প্যারামেট্রিক পলিমরফিজম ============
# একটিই ফাংশন, কোনো টাইপ-নির্দিষ্ট branch নেই -- int-এর লিস্ট বা string-এর লিস্ট, দুটোতেই অভিন্নভাবে কাজ করে
def first_element(a_list):
return a_list[0]
int_list = [10, 20, 30]
str_list = ["আম", "জাম", "কাঁঠাল"]
print("=== (১) প্যারামেট্রিক পলিমরফিজম ===")
print("first_element(int_list) =", first_element(int_list))
print("first_element(str_list) =", first_element(str_list))
print("(একই ফাংশন সংজ্ঞা, কোনো if isinstance(...) শাখা নেই)\n")
# ============ (২) অ্যাড-হক পলিমরফিজম (ওভারলোডিং) ============
# একই নাম combine(), কিন্তু আর্গুমেন্ট টাইপ অনুযায়ী সত্যিকারভাবে ভিন্ন লজিক
def combine(a, b):
if isinstance(a, int) and isinstance(b, int):
return a + b # (int, int) -> পাটিগণিতিক যোগ
if isinstance(a, str) and isinstance(b, str):
return a + " " + b # (str, str) -> স্পেস দিয়ে concatenation
raise TypeError(f"combine()-এর জন্য কোনো বাস্তবায়ন নেই: {type(a).__name__}, {type(b).__name__}")
print("=== (২) অ্যাড-হক পলিমরফিজম (ওভারলোডিং) ===")
print("combine(3, 4) =", combine(3, 4))
print("combine('ভালো', 'আছি') =", combine("ভালো", "আছি"))
print("(দুটো ভিন্ন if-branch, দুটো ভিন্ন প্রকৃত লজিক -- এটাই ডিসপ্যাচ)\n")
# ============ (৩) সাবটাইপ পলিমরফিজম ============
# L07-এর Shape/Rectangle/Circle হায়ারার্কি পুনরায় ব্যবহার করা হচ্ছে
class Shape:
def area(self):
raise NotImplementedError("প্রতিটি সাবটাইপকে area() ওভাররাইড করতে হবে")
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def total_area(shapes_list):
"""Liskov Substitution Principle -- এই ফাংশনটি জানে না, জানার প্রয়োজনও নেই, তালিকায় কোন কংক্রিট সাবটাইপ আছে।"""
return sum(shape.area() for shape in shapes_list)
shapes = [Rectangle(3, 4), Circle(2), Rectangle(5, 2)]
print("=== (৩) সাবটাইপ পলিমরফিজম ===")
for shape in shapes:
print(f"{type(shape).__name__}.area() = {shape.area():.3f}")
result = total_area(shapes)
print(f"total_area(shapes) = {result:.3f}")
expected = (3*4) + (math.pi * 2**2) + (5*2)
assert abs(result - expected) < 1e-9, "total_area ভুল!"
print(f"হাতে-হিসাব করা প্রত্যাশিত মান: {expected:.3f} -- মিলে গেছে।")
first_element-এ কোনো
টাইপ-চেকিং if স্টেটমেন্টই নেই (প্যারামেট্রিক)। combine-এ ঠিক দুটো সম্পূর্ণ
আলাদা রিটার্ন এক্সপ্রেশন আছে, টাইপ দিয়ে গার্ড করা (অ্যাড-হক)। total_area-এ কোনো
isinstance চেকই নেই — এটি শুধু .area() কল করে এবং প্রতিটি সাবটাইপ নিজের সঠিক
বাস্তবায়ন সরবরাহ করে (সাবটাইপ) — তিনটি সম্পূর্ণ ভিন্ন কোড-প্যাটার্ন, তিনটি ভিন্ন ধারণার জন্য।
"পলিমরফিজম" একটি শব্দ কিন্তু তিনটি স্বতন্ত্র মেকানিজম — প্যারামেট্রিক (একটি কোড, সব টাইপ), অ্যাড-হক (অনেক কোড, টাইপ-ডিসপ্যাচড), সাবটাইপ (হায়ারার্কি-ভিত্তিক বিনিময়যোগ্যতা)। এই তিনটি গুলিয়ে ফেলাই সবচেয়ে সাধারণ ভুল — উপরের কোডের কাঠামোগত পার্থক্যটি মনে রাখলে সেই বিভ্রান্তি এড়ানো সহজ হয়। L36-এ দেখা যাবে একটি টাইপ সিস্টেম এই পলিমরফিক অপারেশনগুলোর টাইপ-নিরাপত্তার প্রতিশ্রুতি আসলে রক্ষা করছে কিনা তা কীভাবে যাচাই করা হয়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
যদি combine()-এ একটি তৃতীয় শাখা যোগ করা হয় — (list, list)-এর জন্য দুটো লিস্ট concatenate করা — তাহলে এটি কি এখনো অ্যাড-হক পলিমরফিজম, নাকি প্যারামেট্রিক হয়ে যাবে?
এখনো অ্যাড-হক পলিমরফিজম — কারণ প্রতিটি টাইপ জোড়ার জন্য এখনো একটি আলাদা, নির্দিষ্ট
if-শাখা লেখা হচ্ছে (এখন তিনটি শাখা, দুটোর বদলে)। প্যারামেট্রিক পলিমরফিজম হতো যদি একটিই
কোড-পথ, টাইপ পরীক্ষা না করেই, list/int/str সবকিছুর জন্য কাজ করত — যেমন a + b লিখলে যদি
Python নিজেই দুটো লিস্ট, দুটো int, দুটো str-এর জন্য একই +-এর সংজ্ঞা প্রয়োগ করত
(এটি আসলে না — Python-এর নিজের +-ও প্রতিটি টাইপের জন্য আলাদাভাবে সংজ্ঞায়িত, তাই এটিও
প্রযুক্তিগতভাবে অ্যাড-হক)।
প্র ০২
উপরের কোড সেলে shapes-এর তালিকায় একটি নতুন সাবটাইপ, যেমন Triangle (নিজস্ব area() সহ) যোগ করলে total_area() ফাংশনের কোড কি পরিবর্তন করতে হবে?
না — এটিই Liskov Substitution Principle-এর প্রকৃত শক্তি। total_area() শুধু
shape.area() কল করে, কখনো isinstance() দিয়ে চেক করে না কোন নির্দিষ্ট সাবটাইপ
এটি। যতক্ষণ Triangle Shape-এর একটি বৈধ সাবটাইপ (নিজস্ব সঠিক
area() ওভাররাইড সহ), ততক্ষণ এটি বিদ্যমান কোড না পাল্টেই তালিকায় ব্যবহারযোগ্য —
এটিই সাবটাইপ পলিমরফিজমের সবচেয়ে বাস্তবসম্মত সুবিধা: এক্সটেনসিবিলিটি বিদ্যমান কোড না ভেঙে।
প্র ০৩
L34-এর unify()-এর সাথে প্যারামেট্রিক পলিমরফিজমের সম্পর্ক কী?
একটি জেনেরিক ফাংশন যেমন first_element-এর টাইপ স্বাভাবিকভাবে লেখা হয় একটি টাইপ ভেরিয়েবল
দিয়ে — যেমন "List<T> → T, যেকোনো T-এর জন্য।" যখন এই ফাংশনটি একটি নির্দিষ্ট কলে
ব্যবহার করা হয় (যেমন first_element(int_list)), টাইপ ইনফারেন্স/ইউনিফিকেশন সেই
T-কে সেই নির্দিষ্ট কলের জন্য int-এর সাথে ইউনিফাই করে দেয় — ঠিক L34-এর
unify()-এর মতোই একটি প্রক্রিয়ায়। তাই প্যারামেট্রিক পলিমরফিজম ও টাইপ ইনফারেন্স ঘনিষ্ঠভাবে
সংযুক্ত — একটি জেনেরিক টাইপ লেখার সুযোগ দেয়, অন্যটি প্রতিটি নির্দিষ্ট ব্যবহারে সেই জেনেরিক টাইপকে
স্বয়ংক্রিয়ভাবে ইনস্ট্যান্সিয়েট করে।
অনুশীলন
-
পরীক্ষা করুন: উপরের কোড সেলে
combine(3, "x")(একটি int ও একটি str) কল করে দেখুন — এটি কী এরর দেয়, এবং কেন এই এররটি অ্যাড-হক পলিমরফিজমের সংজ্ঞার সাথে সামঞ্জস্যপূর্ণ?এটি একটি
TypeErrorছুড়বে ("combine()-এর জন্য কোনো বাস্তবায়ন নেই: int, str") — কারণcombine-এ শুধু (int, int) ও (str, str) জোড়ার জন্য নির্দিষ্ট বাস্তবায়ন সংজ্ঞায়িত করা হয়েছে, (int, str) মিশ্র জোড়ার জন্য কোনো বাস্তবায়ন নেই। এটি অ্যাড-হক পলিমরফিজমের একটি গুরুত্বপূর্ণ বৈশিষ্ট্য স্পষ্ট করে — এটি শুধু যে নির্দিষ্ট জোড়ার জন্য একটি বাস্তবায়ন লেখা হয়েছে তার জন্যই কাজ করে, প্যারামেট্রিক পলিমরফিজমের মতো "সব টাইপের জন্য স্বয়ংক্রিয়ভাবে" কাজ করে না। -
চিন্তা করুন: Python-এর বিল্ট-ইন
len()ফাংশন — যা লিস্ট, স্ট্রিং, ডিকশনারি সবকিছুর দৈর্ঘ্য বের করে — এটি কি প্যারামেট্রিক নাকি অ্যাড-হক পলিমরফিজমের উদাহরণ?বাস্তবে এটি অ্যাড-হকের কাছাকাছি (যদিও ভিন্নভাবে বাস্তবায়িত) —
len()ভেতরে ভেতরে প্রতিটি টাইপের নিজস্ব__len__মেথড কল করে, আর প্রতিটি টাইপ (list, str, dict) তার নিজস্ব, ভিন্ন__len__বাস্তবায়ন সরবরাহ করে (একটি list-এর__len__তার উপাদান গোনে, একটি dict-এর__len__তার key গোনে — সত্যিকারভাবে ভিন্ন লজিক, শুধু একই নামে ডাকা হয়) — এটি বরং সাবটাইপ পলিমরফিজম ও অ্যাড-হক পলিমরফিজমের একটি সংমিশ্রণের কাছাকাছি, যাকে প্রায়ই "ডাক টাইপিং"ও বলা হয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ পরবর্তী পাঠ — টাইপ সেফটি ও সাউন্ডনেস — "well-typed programs don't go wrong" নীতির পূর্ণাঙ্গ প্রমাণ কাঠামো, শীঘ্রই।
- L07 · অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং প্যারাডাইম পূর্বশর্ত Shape/Rectangle/Circle হায়ারার্কি ও OOP পলিমরফিজমের সংক্ষিপ্ত পরিচিতি এই পাঠেই প্রথম দেখানো হয়েছিল — এই পাঠ সেটিকেই পূর্ণাঙ্গ টাইপ-থিওরি নির্ভুলতায় নিয়ে গেল।
- L34 · টাইপ ইনফারেন্স সম্পর্কিত পাঠ প্যারামেট্রিক পলিমরফিজম ও টাইপ ইনফারেন্স ঘনিষ্ঠভাবে সংযুক্ত — ইউনিফিকেশনই জেনেরিক কোডের নির্দিষ্ট ব্যবহারে টাইপ ভেরিয়েবল সমাধান করে।