পাঠ ৩৫ · ৫৮-এর মধ্যে · মডিউল ৭

পলিমরফিজম — প্যারামেট্রিক, অ্যাড-হক ও সাবটাইপ

Polymorphism — parametric, ad-hoc & subtype
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • তিন ধরনের পলিমরফিজমের সুনির্দিষ্ট সংজ্ঞা এবং একে অপরের থেকে তাদের পার্থক্য
  • প্যারামেট্রিক পলিমরফিজম — একটি সত্যিকারের জেনেরিক ফাংশনের বাস্তবায়ন
  • অ্যাড-হক পলিমরফিজম — একটি টাইপ-ডিসপ্যাচড ফাংশনের বাস্তবায়ন
  • সাবটাইপ পলিমরফিজম — 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 হায়ারার্কিই পুনরায় ব্যবহার করা হচ্ছে, যাতে ধারাবাহিকতা বজায় থাকে।

Python
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() কল করে এবং প্রতিটি সাবটাইপ নিজের সঠিক বাস্তবায়ন সরবরাহ করে (সাবটাইপ) — তিনটি সম্পূর্ণ ভিন্ন কোড-প্যাটার্ন, তিনটি ভিন্ন ধারণার জন্য।
মূল কথা · Key takeaway

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

অনুশীলন

  1. পরীক্ষা করুন: উপরের কোড সেলে combine(3, "x") (একটি int ও একটি str) কল করে দেখুন — এটি কী এরর দেয়, এবং কেন এই এররটি অ্যাড-হক পলিমরফিজমের সংজ্ঞার সাথে সামঞ্জস্যপূর্ণ?

    এটি একটি TypeError ছুড়বে ("combine()-এর জন্য কোনো বাস্তবায়ন নেই: int, str") — কারণ combine-এ শুধু (int, int) ও (str, str) জোড়ার জন্য নির্দিষ্ট বাস্তবায়ন সংজ্ঞায়িত করা হয়েছে, (int, str) মিশ্র জোড়ার জন্য কোনো বাস্তবায়ন নেই। এটি অ্যাড-হক পলিমরফিজমের একটি গুরুত্বপূর্ণ বৈশিষ্ট্য স্পষ্ট করে — এটি শুধু যে নির্দিষ্ট জোড়ার জন্য একটি বাস্তবায়ন লেখা হয়েছে তার জন্যই কাজ করে, প্যারামেট্রিক পলিমরফিজমের মতো "সব টাইপের জন্য স্বয়ংক্রিয়ভাবে" কাজ করে না।

  2. চিন্তা করুন: Python-এর বিল্ট-ইন len() ফাংশন — যা লিস্ট, স্ট্রিং, ডিকশনারি সবকিছুর দৈর্ঘ্য বের করে — এটি কি প্যারামেট্রিক নাকি অ্যাড-হক পলিমরফিজমের উদাহরণ?

    বাস্তবে এটি অ্যাড-হকের কাছাকাছি (যদিও ভিন্নভাবে বাস্তবায়িত) — len() ভেতরে ভেতরে প্রতিটি টাইপের নিজস্ব __len__ মেথড কল করে, আর প্রতিটি টাইপ (list, str, dict) তার নিজস্ব, ভিন্ন __len__ বাস্তবায়ন সরবরাহ করে (একটি list-এর __len__ তার উপাদান গোনে, একটি dict-এর __len__ তার key গোনে — সত্যিকারভাবে ভিন্ন লজিক, শুধু একই নামে ডাকা হয়) — এটি বরং সাবটাইপ পলিমরফিজম ও অ্যাড-হক পলিমরফিজমের একটি সংমিশ্রণের কাছাকাছি, যাকে প্রায়ই "ডাক টাইপিং"ও বলা হয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
L34 · টাইপ ইনফারেন্স