প্যারামিটার পাসিং মেকানিজম
এই পাঠে যা শিখবেন
- Pass-by-value কীভাবে caller-এর ডেটাকে isolate রাখে
- Pass-by-reference কীভাবে caller-এর ডেটায় সরাসরি মিউটেশন ঘটায়
- Pass-by-name কেন বিরল, এবং এর সারপ্রাইজিং কনসিকোয়েন্স কী
- একই modifier ফাংশন দিয়ে by-value ও by-reference-এর পার্থক্য বাস্তব কোডে যাচাই
১ · অ্যাক্টিভেশন রেকর্ডের প্যারামিটার স্লট — কীভাবে ভরা হয়
M11/L48-এ দেখা গেছে, প্রতিটি অ্যাক্টিভেশন রেকর্ডে একটি প্যারামিটার স্লট থাকে। কিন্তু ঠিক কীভাবে caller-এর আর্গুমেন্ট মান সেই স্লটে "ভরা" হয় — সেই ভিন্ন-ভিন্ন কৌশলগুলোই এই পাঠের বিষয়। ভাষাভেদে এই কৌশল ভিন্ন, এবং এই পার্থক্যের সরাসরি, বাস্তব প্রভাব আছে callee কী করতে পারবে তার উপর।
Callee একটি কপি পায়। Isolation guarantee — callee কখনো caller-এর অরিজিনাল বদলাতে পারে না।
Callee caller-এর storage-এর একটি alias পায়। Callee-এর মিউটেশন caller-এও দেখা যায়।
প্রতিবার ব্যবহারে এক্সপ্রেশন পুনরায় ইভালুয়েট হয় — সাইড-ইফেক্ট থাকলে প্রতিবার ভিন্ন ফল দিতে পারে।
২ · Pass-by-value — কপি ও আইসোলেশন
Pass-by-valuePass-by-valueCallee আর্গুমেন্টের মানের একটি কপি পায় — callee-এর পরিবর্তন caller-এর অরিজিনালকে প্রভাবিত করে না। -এ callee আর্গুমেন্টের মানের একটি কপি পায় — callee নিজের কপিতে যা-ই পরিবর্তন করুক না কেন, caller-এর অরিজিনাল ভ্যারিয়েবল অপরিবর্তিত থাকে। এটি একটি গুরুত্বপূর্ণ আইসোলেশন গ্যারান্টি: একটি ফাংশন কল করার পরও caller নিশ্চিত থাকতে পারে তার নিজের ডেটা অক্ষত আছে (যদি না ফাংশনটি স্পষ্টভাবে একটি নতুন মান রিটার্ন করে caller নিজেই সেটি assign করে)।
৩ · Pass-by-reference — সরাসরি একই স্টোরেজে অ্যাক্সেস
Pass-by-referencePass-by-referenceCallee caller-এর অরিজিনাল ভ্যারিয়েবলের একটি রেফারেন্স/alias পায় — একই আন্ডারলাইং স্টোরেজে কাজ করে। -এ callee caller-এর অরিজিনাল ভ্যারিয়েবলের একটি রেফারেন্স (কার্যত একটি alias) পায় — callee যা পরিবর্তন করে তা সরাসরি caller-এর অরিজিনালেও প্রতিফলিত হয়, কারণ উভয়েই একই আন্ডারলাইং স্টোরেজে কাজ করছে, কপিতে নয়। এটি একটি সত্যিকারের ভিন্ন, বেশি permissive সিমান্টিক্স — একটি ফাংশন ইচ্ছাকৃতভাবে (ভালো বা মন্দ, উভয় অর্থেই) তার caller-এর ভ্যারিয়েবল বদলে দিতে পারে এই মেকানিজমের মাধ্যমে।
৪ · Pass-by-name — ঐতিহাসিক, বিরল, বিস্ময়কর
Pass-by-namePass-by-nameআর্গুমেন্ট এক্সপ্রেশনটি নিজেই (তার ইভালুয়েটেড মান নয়) পাস হয়, এবং callee-এর ভেতরে প্রতিবার ব্যবহারের সময় নতুন করে ইভালুয়েট হয়। একটি ঐতিহাসিকভাবে গুরুত্বপূর্ণ কিন্তু এখন বিরল মেকানিজম — এখানে আর্গুমেন্ট এক্সপ্রেশনটি নিজেই (তার ইভালুয়েটেড মান নয়, এবং একটি সাধারণ রেফারেন্সও নয়) পাস হয়, এবং callee-এর ভেতরে প্যারামিটারটি যতবার ব্যবহৃত হয় ততবারই সেই এক্সপ্রেশন নতুন করে ইভালুয়েট হয়। এর বাস্তব, বিস্ময়কর ফলাফল: যদি আর্গুমেন্ট এক্সপ্রেশনে সাইড-ইফেক্ট থাকে, বা এমন একটি ভ্যারিয়েবল রেফার করে যা callee চলাকালীন বদলে যায়, তাহলে প্রতিটি ব্যবহার ভিন্ন ভিন্ন ফলাফল দিতে পারে — এটাই মূল কারণ কেন আধুনিক বেশিরভাগ ভাষা এই মেকানিজম এড়িয়ে চলে।
৫ · কোড: by-value বনাম by-reference যাচাই
Python-এর নিজস্ব ডিফল্ট আচরণ আসলে অবজেক্ট-রেফারেন্স পাস করা — তাই সত্যিকারের pass-by-value
দেখানোর জন্য নিচের কোডে স্পষ্টভাবে copy.deepcopy দিয়ে কপি তৈরি করা হয়েছে। এরপর একই
modifier_fn ও একই স্টার্টিং লিস্ট দিয়ে দুটো মেকানিজম চালিয়ে caller-এর লিস্টের অবস্থা যাচাই
করা হয়েছে।
import copy
def call_by_value(original_list, modifier_fn):
"""callee একটি deep copy-তে কাজ করে -- caller-এর original অস্পৃশ্য থাকে।"""
local_copy = copy.deepcopy(original_list)
modifier_fn(local_copy)
return original_list, local_copy
def call_by_reference(original_list, modifier_fn):
"""callee সরাসরি caller-এর original object-এ কাজ করে।"""
modifier_fn(original_list)
return original_list
def append_99(lst):
lst.append(99)
# --- Pass-by-value ---
caller_list_a = [1, 2, 3]
print(f"call_by_value-এর আগে caller_list_a = {caller_list_a}")
original_a, copy_a = call_by_value(caller_list_a, append_99)
print(f"call_by_value-এর পরে caller_list_a = {caller_list_a} (অপরিবর্তিত থাকা উচিত)")
print(f"callee-এর ভেতরের কপি ছিল = {copy_a} (এখানেই পরিবর্তন ঘটেছে)")
assert caller_list_a == [1, 2, 3]
assert copy_a == [1, 2, 3, 99]
# --- Pass-by-reference ---
caller_list_b = [1, 2, 3]
print(f"\ncall_by_reference-এর আগে caller_list_b = {caller_list_b}")
call_by_reference(caller_list_b, append_99)
print(f"call_by_reference-এর পরে caller_list_b = {caller_list_b} (পরিবর্তিত হওয়া উচিত)")
assert caller_list_b == [1, 2, 3, 99]
print("\nসারাংশ:")
print(f" by-value -> caller অপরিবর্তিত: {caller_list_a == [1, 2, 3]}")
print(f" by-reference -> caller পরিবর্তিত : {caller_list_b == [1, 2, 3, 99]}")
call_by_value ও call_by_reference দুটোই একই
modifier_fn (append_99) ব্যবহার করছে। পার্থক্যটা modifier ফাংশনে নেই — পার্থক্যটা
caller ও callee-এর মধ্যে কোন object শেয়ার হচ্ছে তাতে: call_by_value একটি
সম্পূর্ণ আলাদা কপিতে modifier চালায়, call_by_reference একই, শেয়ার্ড object-এ।
Parameter passing মেকানিজম নির্ধারণ করে callee ও caller একই storage শেয়ার করে কিনা। Pass-by-value শেয়ার করে না (আইসোলেশন), pass-by-reference শেয়ার করে (মিউটেশন দৃশ্যমান), আর pass-by-name তো মানই শেয়ার করে না — এক্সপ্রেশন নিজেই শেয়ার করে, যা প্রতিবার নতুন করে ইভালুয়েট হয়ে বিস্ময়কর আচরণ তৈরি করতে পারে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
উপরের কোডে call_by_value কেন copy.deepcopy ব্যবহার করছে, শুধু সাধারণভাবে local_copy = original_list লিখলে চলত না কেন?
Python-এ local_copy = original_list নতুন কোনো object তৈরি করে না — এটি শুধু একই list
object-এর দিকে একটি দ্বিতীয় নাম যোগ করে (Python-এর ডিফল্ট আচরণই object-রেফারেন্স পাস করা)। ফলে
local_copy.append(99) করলে সেই একই, শেয়ার্ড object বদলে যেত — অর্থাৎ ভুলবশত
pass-by-reference-এর আচরণ দেখাত। copy.deepcopy সত্যিই একটি সম্পূর্ণ নতুন, স্বাধীন object
বানায়, যা pass-by-value-এর প্রকৃত isolation semantics-কে সঠিকভাবে সিমুলেট করে।
প্র ০২
Pass-by-name-এ যদি আর্গুমেন্ট এক্সপ্রেশন হয় a[i] (একটি array ইনডেক্সিং), এবং callee চলাকালীন i-এর মান বদলে যায়, তাহলে কী সমস্যা হতে পারে?
যেহেতু pass-by-name-এ এক্সপ্রেশন a[i] প্রতিবার ব্যবহারের সময় নতুন করে
ইভালুয়েট হয়, callee-এর ভেতরে i বদলে গেলে একই প্যারামিটার নাম প্রতিবার ভিন্ন ভিন্ন
এলিমেন্ট নির্দেশ করতে পারে — একবার a[2], পরের ব্যবহারে হয়তো a[5]।
এটি একটি ক্লাসিক, বাস্তব বাগ-উৎস (একে "Jensen's Device" নামেও ঐতিহাসিকভাবে চেনা যায়) — এবং ঠিক এই
কারণেই আধুনিক ভাষাগুলো pass-by-name পরিহার করে, predictable pass-by-value/reference-কেই ডিফল্ট রাখে।
প্র ০৩
যদি append_99-এর বদলে একটি এমন ফাংশন ব্যবহার করা হয় যা প্যারামিটারকে সম্পূর্ণ নতুন একটি list দিয়ে rebind করে (যেমন lst = [0, 0, 0], append নয়), তাহলে pass-by-reference-এও কি caller-এর লিস্ট বদলাবে?
না। lst = [0, 0, 0] callee-এর ভেতরে শুধু স্থানীয় নাম lst-কে একটি সম্পূর্ণ
নতুন object-এর দিকে rebind করে — এটি অরিজিনাল, caller-এর object-কে ইন-প্লেস
মিউটেট করে না। Pass-by-reference caller-এর object-টিকে মিউটেট (যেমন .append(),
[i]=) করলেই caller দেখে — কিন্তু callee-এর স্থানীয় নাম rebind করা সবসময় শুধু সেই
স্থানীয় নামকেই প্রভাবিত করে, caller-এর ভ্যারিয়েবলকে নয়। এই সূক্ষ্ম পার্থক্যটি বাস্তব প্রোগ্রামিংয়ের
একটি সাধারণ বিভ্রান্তির উৎস।
অনুশীলন
-
পরীক্ষা করুন: উপরের কোডে
append_99-এর বদলে একটি নতুনmodifier_fnলিখুন যা লিস্টের প্রথম এলিমেন্ট মুছে ফেলে (lst.pop(0)), এবংcall_by_value/call_by_referenceদুটোতেই চালিয়ে ফলাফল যাচাই করুন।একই প্যাটার্ন পুনরাবৃত্তি হবে:
call_by_value([1,2,3], remove_first)-এর পর caller-এর লিস্ট[1, 2, 3]-ই থাকবে (অপরিবর্তিত), কিন্তু callee-এর কপি হবে[2, 3]।call_by_reference([1,2,3], remove_first)-এর পর caller-এর লিস্ট সরাসরি[2, 3]-এ বদলে যাবে — মেকানিজমটি operation-নিরপেক্ষ (append হোক বা pop, একই isolation/mutation নিয়ম প্রযোজ্য)। -
চিন্তা করুন: Python-এর একটি সাধারণ
intভ্যারিয়েবল (যেমনx = 5) একটি ফাংশনে পাস করলে এবং callee-এর ভেতরেx += 1করলে caller-এরxকি বদলায়? লিস্টের সাথে এই আচরণের পার্থক্য কেন?না, caller-এর
xবদলাবে না — কারণ Python-এ integer একটি ইমিউটেবল টাইপ।x += 1callee-এর ভেতরে আসলে callee-এর স্থানীয় নামx-কে একটি সম্পূর্ণ নতুন int object-এর দিকে rebind করে (ঠিক প্র-০৩-এরlst = [0,0,0]-এর মতোই) — অরিজিনাল int object মিউটেট হয় না (হতেই পারে না, কারণ int ইমিউটেবল)। লিস্ট (mutable) হলে.append()-এর মতো ইন-প্লেস মিউটেশন সম্ভব বলেই pass-by-reference-এর প্রভাব দৃশ্যমান হয় — এই পার্থক্যটাই দেখায় কেন Python-এর প্যারামিটার-পাসিং আচরণ "value নাকি reference" প্রশ্নের চেয়ে বেশি "টাইপটি mutable কিনা" তার উপর নির্ভর করে বলে মনে হয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ পরের পাঠে — এই একই অ্যাক্টিভেশন-রেকর্ড মডেল দিয়ে রিকার্শনের গভীরতা ট্র্যাক করা হবে।
- পূর্ববর্তী পাঠ: অ্যাক্টিভেশন রেকর্ড ও কল স্ট্যাক L48 এই পাঠের প্যারামিটার স্লট ঠিক কোথায় বসে, প্রথমে সেই কাঠামোটি দেখে নিন।
- Computer Architecture & Digital Logic কোর্স সহোদর কোর্স রেজিস্টার বনাম মেমরিতে আর্গুমেন্ট পাস করা — এই মেকানিজমগুলোর হার্ডওয়্যার-লেভেল প্রতিফলন।