টাইপ ইকুইভ্যালেন্স ও চেকিং রুল
এই পাঠে যা শিখবেন
- টাইপ ইকুইভ্যালেন্স কেন একটি গুরুত্বপূর্ণ, সাবটল ডিজাইন প্রশ্ন
- নেম ইকুইভ্যালেন্স — সংজ্ঞা, উদাহরণ ও এর সুবিধা
- স্ট্রাকচারাল ইকুইভ্যালেন্স — সংজ্ঞা, উদাহরণ ও এর সুবিধা
- দুটো নিয়ম কোথায় একমত, কোথায় দ্বিমত — কোড দিয়ে প্রমাণ
১ · কেন এই প্রশ্নটি গুরুত্বপূর্ণ
টাইপ ইকুইভ্যালেন্সType Equivalenceদুটো টাইপ এক্সপ্রেশনকে টাইপ-চেকিং-এর উদ্দেশ্যে "একই টাইপ" হিসেবে বিবেচনা করার নিয়ম। হলো — দুটো টাইপ এক্সপ্রেশনকে টাইপ-চেকিং-এর উদ্দেশ্যে কখন "একই টাইপ" হিসেবে বিবেচনা করা হবে, সেই প্রশ্ন। এটি নিছক তাত্ত্বিক কৌতূহল নয় — L30-এর টাইপ-চেকিং নিয়মগুলো মূলত এই একটি প্রশ্নের উপর দাঁড়িয়ে: "এই দুটো টাইপ কি সামঞ্জস্যপূর্ণ (compatible)?" সেই প্রশ্নের উত্তর দিতে হলে আগে জানতে হবে দুটো টাইপকে "একই" ধরার নিয়মটা ঠিক কী।
২ · নেম ইকুইভ্যালেন্স
নেম ইকুইভ্যালেন্সName Equivalenceদুটো টাইপ একই ধরা হয় শুধুমাত্র যদি তাদের নাম/ডিক্লারেশন হুবহু এক হয়, গঠন যাই হোক না কেন।-এর
নিয়মে দুটো টাইপ একই ধরা হয় শুধুমাত্র যদি তাদের নাম/ডিক্লারেশন হুবহু এক হয়। এমনকি দুটো
টাইপের অভ্যন্তরীণ গঠন হুবহু এক হলেও, যদি সেগুলো আলাদা নামে ডিক্লেয়ার করা হয়, তারা ভিন্ন
টাইপ হিসেবে বিবেচিত হয়। উদাহরণ — type Meters = int; ও type Seconds = int; —
কিছু ভাষায় এই দুটোকে সামঞ্জস্যহীন (incompatible) টাইপ ধরা হয়, যদিও দুটোই ভেতরে ভেতরে শুধুই একটি
int। এটি একটি ইচ্ছাকৃত ডিজাইন সিদ্ধান্ত — যাতে ভুলবশত ভিন্ন ধরনের রাশি (দূরত্ব বনাম সময়)
একসাথে মেশানো না যায়, শুধু তারা একই আন্ডারলাইং রিপ্রেজেন্টেশন শেয়ার করে বলে।
৩ · স্ট্রাকচারাল ইকুইভ্যালেন্স
স্ট্রাকচারাল ইকুইভ্যালেন্সStructural Equivalenceদুটো টাইপ একই ধরা হয় যদি তাদের অভ্যন্তরীণ গঠন একই হয়, নাম যাই হোক না কেন।-এর
নিয়মে দুটো টাইপ একই ধরা হয় যদি তাদের অভ্যন্তরীণ গঠন একই হয় — নাম যাই হোক না কেন। এই
নিয়মে উপরের Meters ও Seconds একই টাইপ ধরা হবে, কারণ দুটোই গঠনগতভাবে "শুধু একটি
int।" স্ট্রাকচারাল ইকুইভ্যালেন্স বেশি নমনীয়/সুবিধাজনক — কিন্তু এটি ঠিক সেই কনসেপচুয়াল মিক্স-আপ অনুমতি দেয়
যা নেম ইকুইভ্যালেন্স আটকাতে চেয়েছিল।
৪ · সৎ ট্রেড-অফ
কনসেপচুয়াল মিক্স-আপ বাগ ধরে (Meters + Seconds আটকায়) — কিন্তু সংগঠনগত কারণে আলাদাভাবে ডিক্লেয়ার করা হুবহু-একই-গঠনের দুটো টাইপের জন্যও অপ্রয়োজনীয়ভাবে কঠোর হতে পারে।
নমনীয়/সুবিধাজনক — গঠন মিললেই চলে — কিন্তু নেম ইকুইভ্যালেন্স যে অর্থপূর্ণ মিক্স-আপ আটকাতো, তা এখানে অনুমোদিত হয়ে যায়।
ফরমালি বললে, একটি টাইপ চেকার একটি নির্দিষ্ট সিদ্ধান্ত ফাংশন প্রয়োগ করে —
$\text{equiv}(\tau_1, \tau_2) \in \{\text{true}, \text{false}\}$ — এবং L30-এর প্রতিটি টাইপ-চেকিং নিয়ম
("অপারেন্ডের টাইপ কি সামঞ্জস্যপূর্ণ?") ঠিক এই equiv ফাংশনটিই কল করে, ব্যাখ্যা না করেই কোন
সংজ্ঞা (নেম নাকি স্ট্রাকচারাল) ব্যবহার করা হচ্ছে — সংজ্ঞাটি ভাষা-ডিজাইনার আগে থেকেই বেছে নেন।
৫ · কোড দিয়ে যাচাই — Meters/Seconds বনাম ভিন্ন-গঠনের রেকর্ড
নিচের কোড সেলে দুটো ইকুইভ্যালেন্স ফাংশনই বাস্তবায়ন করা হয়েছে এবং দুটো ভিন্ন টেস্ট কেসে চালানো হয়েছে — একটিতে নেম ও স্ট্রাকচারাল দ্বিমত করবে (Meters বনাম Seconds), অন্যটিতে দুটোই একমত হবে (সত্যিকারের ভিন্ন-গঠনের দুটো রেকর্ড টাইপ)।
# প্রতিটি টাইপকে একটি dict হিসেবে রিপ্রেজেন্ট করা হচ্ছে: {'name': ..., 'structure': ...}
# স্কেলার টাইপের জন্য structure একটি ট্যাগড টুপল; রেকর্ডের জন্য (field_name, field_type) জোড়ার sorted টুপল।
def types_equal_by_name(type_a, type_b):
"""নেম ইকুইভ্যালেন্স -- শুধু ডিক্লেয়ার্ড নাম তুলনা করে, গঠন উপেক্ষা করে।"""
return type_a['name'] == type_b['name']
def types_equal_by_structure(type_a, type_b):
"""স্ট্রাকচারাল ইকুইভ্যালেন্স -- শুধু অভ্যন্তরীণ গঠন তুলনা করে, নাম উপেক্ষা করে।"""
return type_a['structure'] == type_b['structure']
# --- টেস্ট ১: Meters বনাম Seconds -- আলাদা নাম, কিন্তু ভেতরে ভেতরে দুটোই শুধুই 'int' ---
meters = {'name': 'Meters', 'structure': ('base', 'int')}
seconds = {'name': 'Seconds', 'structure': ('base', 'int')}
name_result_1 = types_equal_by_name(meters, seconds)
struct_result_1 = types_equal_by_structure(meters, seconds)
print("=== টেস্ট ১: Meters বনাম Seconds ===")
print(f"নেম ইকুইভ্যালেন্স: {name_result_1} (আলাদা নাম -> আলাদা টাইপ)")
print(f"স্ট্রাকচারাল ইকুইভ্যালেন্স: {struct_result_1} (দুটোই ভেতরে 'int' -> একই টাইপ)")
print("-> দ্বিমত (disagree), ঠিক যেমনটা প্রত্যাশিত ছিল\n")
# --- টেস্ট ২: দুই সত্যিকারের ভিন্ন-গঠনের রেকর্ড -- Point2D (x,y) বনাম Point3D (x,y,z) ---
point2d = {'name': 'Point2D', 'structure': (('x', 'int'), ('y', 'int'))}
point3d = {'name': 'Point3D', 'structure': (('x', 'int'), ('y', 'int'), ('z', 'int'))}
name_result_2 = types_equal_by_name(point2d, point3d)
struct_result_2 = types_equal_by_structure(point2d, point3d)
print("=== টেস্ট ২: Point2D বনাম Point3D ===")
print(f"নেম ইকুইভ্যালেন্স: {name_result_2} (আলাদা নাম -> আলাদা টাইপ)")
print(f"স্ট্রাকচারাল ইকুইভ্যালেন্স: {struct_result_2} (২ ফিল্ড বনাম ৩ ফিল্ড -> সত্যিই আলাদা গঠন -> আলাদা টাইপ)")
agree = (name_result_2 == struct_result_2)
print(f"-> একমত (agree)? {agree}")
assert name_result_1 != struct_result_1, "টেস্ট ১-এ দ্বিমত হওয়ার কথা ছিল!"
assert name_result_2 == struct_result_2, "টেস্ট ২-এ একমত হওয়ার কথা ছিল!"
print("\nদুটো assertion-ই পাস করেছে -- দুই ইকুইভ্যালেন্স নিয়ম ঠিক প্রত্যাশিতভাবেই আচরণ করেছে।")
টাইপ ইকুইভ্যালেন্স একটি ফ্রি সিদ্ধান্ত নয় — এটি সরাসরি প্রভাবিত করে কোন প্রোগ্রাম কম্পাইল হবে আর কোনটি বাতিল হবে। নেম ইকুইভ্যালেন্স কঠোর কিন্তু নিরাপদ; স্ট্রাকচারাল ইকুইভ্যালেন্স নমনীয় কিন্তু কম সতর্ক। বাস্তব ভাষাগুলো (Java-এর ক্লাস নেম ইকুইভ্যালেন্স ব্যবহার করে, TypeScript-এর অবজেক্ট টাইপ মূলত স্ট্রাকচারাল) দুটোই ব্যবহার করে — L34-এর টাইপ ইনফারেন্স এই তুলনার প্রক্রিয়াটিকে স্বয়ংক্রিয় করার একটি সরাসরি পরবর্তী ধাপ।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
যদি একটি ভাষা নেম ইকুইভ্যালেন্স ব্যবহার করে, তাহলে type A = int; ও সরাসরি int ব্যবহার করা একটি ভ্যারিয়েবল কি সামঞ্জস্যপূর্ণ হবে? কেন এটি একটি গুরুত্বপূর্ণ কিনারার কেস (edge case)?
এটি নির্ভর করে ভাষা "নাম" ঠিক কীভাবে সংজ্ঞায়িত করে — কিছু ভাষা মূল বিল্ট-ইন টাইপ (int)-কে
একটি বিশেষ কেস হিসেবে ট্রিট করে যা সবসময় নিজের সাথে সামঞ্জস্যপূর্ণ, কিন্তু একটি user-defined alias
(A) তার সাথেও সামঞ্জস্যপূর্ণ কিনা তা ভাষাভেদে ভিন্ন হতে পারে। এই কিনারার কেসটি গুরুত্বপূর্ণ
কারণ এটি দেখায় "নেম ইকুইভ্যালেন্স" কথাটি শুনতে যতটা সহজ, বাস্তব ভাষা স্পেসিফিকেশনে এর বাস্তবায়ন ঠিক
কতটা সূক্ষ্ম বিস্তারিত সিদ্ধান্তের উপর নির্ভর করে।
প্র ০২
উপরের কোড সেলে যদি point2d-এর structure-কে (('x','int'), ('y','int')) থেকে (('y','int'), ('x','int')) (শুধু ফিল্ড অর্ডার পাল্টে) বদলানো হয়, তাহলে types_equal_by_structure কী ফলাফল দেবে যদি এটি point3d-এর সাথে তুলনা করা হয় — এবং একটি ২-ফিল্ড রেকর্ডের সাথে (একই দুই ফিল্ড, ভিন্ন অর্ডারে) তুলনা করা হলে কী হবে?
point3d-এর সাথে তুলনায় এখনো False আসবে — কারণ ফিল্ড সংখ্যাই (২ বনাম ৩)
আলাদা, অর্ডার যাই হোক না কেন। কিন্তু একটি ভিন্ন ২-ফিল্ড রেকর্ডের সাথে (একই দুই ফিল্ড, উল্টো অর্ডারে)
তুলনা করলে, উপরের কোডের সরল টুপল-তুলনা এদের ভিন্ন ধরবে (কারণ টুপল অর্ডার-সংবেদনশীল),
যদিও অনেক বাস্তব ভাষায় ফিল্ড অর্ডার ভিন্ন হলেও রেকর্ড দুটোকে স্ট্রাকচারালি সমতুল্য ধরা হয়। এটি দেখায়
"স্ট্রাকচারাল ইকুইভ্যালেন্স" বাস্তবায়ন করার সময় ফিল্ড অর্ডার সংবেদনশীল নাকি অসংবেদনশীল — এটিও একটি
সুস্পষ্ট ডিজাইন সিদ্ধান্ত, ডিফল্টে দেওয়া কিছু নয়।
প্র ০৩
L30-এর টাইপ-চেকিং উদাহরণ "hello" + 5-কে বাতিল করার সিদ্ধান্তটি কি এই পাঠের ইকুইভ্যালেন্স প্রশ্নের সাথে সরাসরি সম্পর্কিত?
হ্যাঁ, সরাসরি। + অপারেটরের নিয়ম বলে উভয় অপারেন্ডকে "সামঞ্জস্যপূর্ণ" হতে হবে — আর
"সামঞ্জস্যপূর্ণ" নির্ধারণ করতে হলে চেকারকে জানতে হয় str টাইপ ও int টাইপ
একই ইকুইভ্যালেন্স ক্লাসে পড়ে কিনা। কোনো যুক্তিসঙ্গত ইকুইভ্যালেন্স নিয়মেই (নেম বা স্ট্রাকচারাল, কোনোটাতেই)
str ও int একই টাইপ ধরা হয় না — তাই equiv(str, int) = false,
এবং সেই কারণেই L30-এর টাইপ চেকার এই অপারেশনটি বাতিল করে।
অনুশীলন
-
পরীক্ষা করুন: উপরের কোড সেলে একটি তৃতীয় টেস্ট কেস যোগ করুন — দুটো রেকর্ড টাইপ যাদের নাম আলাদা কিন্তু ফিল্ড সংখ্যা ও টাইপ হুবহু এক (যেমন
Point2DওCoordinate, দুটোই(('x','int'), ('y','int')))। নেম ও স্ট্রাকচারাল ইকুইভ্যালেন্স কী বলবে?নেম ইকুইভ্যালেন্স
Falseদেবে (আলাদা নাম), স্ট্রাকচারাল ইকুইভ্যালেন্সTrueদেবে (হুবহু একই ফিল্ড গঠন) — ঠিক Meters/Seconds উদাহরণের মতোই একটি দ্বিমত। এটি দেখায় Meters/Seconds কোনো বিশেষ কেস নয় — যেকোনো দুটো ভিন্ন-নামের কিন্তু একই-গঠনের টাইপের জন্যই এই দ্বিমত ঘটবে, রেকর্ড হোক বা স্কেলার। -
চিন্তা করুন: আপনার পরিচিত কোনো ভাষায় (Python, Java, TypeScript, C ইত্যাদি) দুটো ক্লাস/টাইপ ডিক্লেয়ার করলে সেগুলো নেম নাকি স্ট্রাকচারাল ইকুইভ্যালেন্স অনুসরণ করে বলে আপনার মনে হয়? একটি সংক্ষিপ্ত উদাহরণ দিয়ে যুক্তি দিন।
সাধারণ পর্যবেক্ষণ: Java ও Python-এ দুটো আলাদা ক্লাস (এমনকি হুবহু একই ফিল্ড/মেথড থাকলেও) কখনোই একে অপরের বদলে গ্রহণযোগ্য নয় — এটি মূলত নেম (নমিনাল) ইকুইভ্যালেন্সের আচরণ। TypeScript-এ দুটো ইন্টারফেস/অবজেক্ট টাইপ, ভিন্ন নামে ডিক্লেয়ার করা হলেও, একই শেপ (ফিল্ড ও টাইপ) থাকলে প্রায়ই একে অপরের বদলে ব্যবহারযোগ্য — এটি স্ট্রাকচারাল ইকুইভ্যালেন্সের একটি সুপরিচিত বাস্তব উদাহরণ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ পরবর্তী পাঠ — টাইপ ইনফারেন্স — প্রোগ্রামারকে টাইপ না লিখেই টাইপ স্বয়ংক্রিয়ভাবে নির্ণয় করা, শীঘ্রই।
- L30 · টাইপ চেকিং বেসিকস পূর্বশর্ত এই পাঠের "সামঞ্জস্যপূর্ণ টাইপ" নিয়মগুলো আসলে এই পাঠের ইকুইভ্যালেন্স প্রশ্নের উপরই দাঁড়িয়ে ছিল।
- L32 · স্ট্যাটিক বনাম ডায়নামিক টাইপিং আগের পাঠ টাইপ ইকুইভ্যালেন্স মূলত স্ট্যাটিক টাইপ-চেকিং-এর একটি অন্তর্নিহিত প্রশ্ন — ডায়নামিক ভাষায় এটি সাধারণত রান-টাইমে ভ্যালুর প্রকৃত টাইপ দিয়ে সমাধান হয়।