Paradigm — imperative, functional, logic, OO: চিন্তার চার ফ্রেম
Programming Paradigms
Paradigm মানে style না — কোন জিনিস ভাষায় বলা যায়, কী নিষিদ্ধ, আর সেই নিষেধের বিনিময়ে কোন প্রমাণ ফ্রি; একই সমস্যা চার ফ্রেমে সমাধান করলে পার্থক্যটা হাতেই টের পাওয়া যায়।
আগে এটা বুঝি
এই module-এ আমরা ভাষার ভেতরটা খুলে দেখেছি — meaning (syntax-vs-semantics, formal-semantics), types, memory, runtime, evaluation order, abnormal control, concurrency, ভাষার মধ্যে সেতু। শেষ লেসনে ফিরে যাই সবচেয়ে বড় প্রশ্নে, যেটা প্রথম দিন থেকেই ঝুলে ছিল: program লেখা আসলে কী?
একই সমস্যা চারজন engineer চার রকম ভাবে লিখতে পারে:
Imperative : "ধাপে ধাপে কর — state বদলাও, loop ঘাও" → C, assembly
Functional : "মান রূপান্তরের বর্ণনা দাও — কী হবে, কীভাবে না" → Haskell, ML
OO : "কে কী জানে, কে কাকে বার্তা পাঠাবে" → Smalltalk, Java
Logic : "সত্য কী, শর্ত কী — খোঁজো engine নিজেই" → Prolog, Datalogএই চার রকম লেখাকে বলে চারটা paradigm — আর বেশিরভাগ বই এটাকে বোঝায় “style” বা “syntax-এর স্বাদ”। সেটা ভুল, আর এই module-এর পুরো যাত্রা কেন ভুল সেটা এখন বলতে পারবেন: syntax-vs-semantics লেসনের মূল শিক্ষাই হলো — surface form আর অর্থ আলাদা জিনিস। Paradigm surface form না; paradigm হলো ভাষার semantic commitment: কোন জিনিসগুলো first-class (variable-এর মতো রাখা-পাঠানো যায়), আর কোন জিনিসগুলো নিষিদ্ধ — কারণ প্রতিটা নিষেধ বিনিময়ে একটা guarantee কেনে।
আজকের প্রশ্ন তিনটা: (১) প্রতিটা paradigm কোন restriction নেয়, আর সেই restriction কী কী ফ্রি দেয়? (২) একই সমস্যা চার ফ্রেমে সমাধান করলে bug-এর চরিত্র কীভাবে বদলায় — কোন ফ্রেমে ভুল করা সবচেয়ে কঠিন? (৩) modern ভাষাগুলো (Rust, Python, Swift) কেন এক paradigm-এ থামে না?
মূল ধারণা
Paradigm-এর ছক — restriction বিনিময়ে guarantee
| Paradigm | First-class citizen | মূল restriction | বিনিময়ে যা ফ্রি |
|---|---|---|---|
| Imperative | command / state change | কোনো না — সব allowed | Machine-এর সাথে সরাসরি mapping; performance predictable |
| Functional | function / value | Mutation নিষিদ্ধ বা সীমিত | Referential transparency → reasoning, memoization, parallelism |
| Object-oriented | object / message | State শুধু নিজের methods দিয়ে ছোঁও যায় | Invariant interface-এর পেছনে সুরক্ষিত; subtype substitution |
| Logic | relation / fact | Control flow explicitly লেখা যায় না | Specification-ই program; search engine চালায় |
এই ছকের শেষ কলামটাই আসল। Restriction ছাড়া guarantee হয় না — আর এই trade-off চেনা মানে প্রতিটা নতুন ভাষা দেখেই বলে দেওয়া কোন promise-টা আপনাকে দিচ্ছে, কোন স্বাধীনতার বিনিময়ে।
Functional — transparency-এর তিন সুফল
Pure function-এর সংজ্ঞা: এক input → এক output, বাইরে কিছু দেখে না বদলায় না। একে বলে referential transparency — যেকোনো expression-কে তার মান দিয়ে replace করা যায়, অর্থ অক্ষত। এই একটা property থেকে তিনটা সুফল গাণিতিকভাবে ঝরে আসে:
১. Equational reasoning : f(f(x)) = f²(x) — সমীকরণ সামলে proof করা যায়;
compiler-ও constant folding/common-subexpression
এভাবেই safe করে
২. Memoization : f(5) একবার কষলে cache করে রাখা নিরাপদ — কারণ
ফল কখনো বদলাবে না (evaluation-strategies লেসনের
thunk-memoization ঠিক এর উপরেই দাঁড়িয়ে)
৩. Parallelism for free : নির্ভরতা না থাকলে যেকোনো জায়গায় ভাগ করা যায় —
race condition-এর concept-ই নেই, কারণ shared
mutable state-ই নেইতৃতীয়টা খেয়াল করুন — concurrency-models লেসনের ছকে shared-state column-এর সব রোগ (race, lock-order deadlock) immutable data-তে structural ভাবে অসম্ভব। Paradigm-সিদ্ধান্ত concurrency-সিদ্ধান্তকে আগেই ঠিক করে দেয়।
OO — encapsulation আর message-passing heritage
type-systems আর polymorphism লেসনে subtype polymorphism দেখেছেন — interface- এর পেছনে যেকোনো implementation ঢুকতে পারে। Paradigm-দৃষ্টিতে OO-র মূল দাবি আরেকটা: state লুকানো, access শুধু message/method দিয়ে। তাহলে invariant (“balance কখনো negative না”) এক জায়গায় রক্ষা করলেই সব জায়গায় রক্ষিত।
History-টা মনে রাখুন — Smalltalk-এ “method call” বলতে সত্যিই message
পাঠানো: receiver decide করে respond করবে কি না (method missing হলেও crash
না, doesNotUnderstand)। এই heritage থেকেই actor model — Erlang-এর actor
আসলে Smalltalk-এর object-এর distributed রূপ, mailbox-সহ। concurrency-models
লেসনের actor column আর আজকের OO ফ্রেম এক বংশের।
Logic — relations, functions না
Functions input→output; relations কয়েকটা জিনিসের মধ্যে সম্পর্ক — যেকোনো
দিক থেকে query করা যায়। parent(alice, bob) দিলে জিজ্ঞেস করা যায় “alice-এর
সন্তান কে?” এবং “bob-এর বাবা কে?” — একই fact, দুই দিক। Engine চালায় দুইটা
যন্ত্র: unification (pattern match করে variable bind) আর backtracking
(choice point ব্যর্থ হলে পেছনে গিয়ে পরের বিকল্প)। বিস্তারিত trace আজকের
example section-এ। Kowalski-র famous সূত্র মনে রাখুন: Algorithm = Logic +
Control — কী সত্য (logic) আর কীভাবে খোঁজা হবে (control) আলাদা করা যায়;
paradigm-টার পুরো দাবি এই separation-এ।
Multi-paradigm reality
Python-এর comprehension functional, class imperative-OO, generator lazy (evaluation-strategies!) — সব মেশানো। Rust: ownership imperative, trait+ iterator functional, pattern matching ML-ঘরানা, actor/channel library-তে। Deliberate design: কোনো এক restriction-ই সব সমস্যায় optimal না — ভালো ভাষা আপনাকে সমস্যা অনুযায়ী ফ্রেম বদলাতে দেয়, আর ভালো engineer জানে কখন বদলাতে হবে। আজকের experiment ঠিক সেই বদলানোর ব্যায়াম।
ভেতরে কী ঘটছে
Closure ↔ object duality
functional আর OO ফ্রেমের মধ্যে একটা গোপন সেতু আছে — closures-and-environments লেসনের জ্ঞান এখানে কাজে লাগবে:
# একই জিনিস, দুই পোশাকে: state + behavior-এর bundle
def make_counter(): # closure-সংস্করণ
count = 0 # captured environment-এ বাস
def step():
nonlocal count
count += 1
return count
return step # behavior + hidden state একসাথে
class Counter: # object-সংস্করণ
def __init__(self):
self.count = 0 # instance field-এ বাস
def step(self):
self.count += 1
return self.countদুটোই “state + সেই state-এর একগুচ্ছ operation”। পার্থক্য শুধু interface: closure একটা operation দেয়, object একগুচ্ছ দেয়; object-এর state-কে multiple methods dispatch করে select করে, closure-এ environment-এর lexical scope-ই selector। SICP-র বিখ্যাত মন্তব্যটা মনে আছে হয়তো: “closures are poor man’s objects, objects are poor man’s closures.” এই duality বোঝা মানে functional/OO-র মধ্যের দেয়ালটা marketing-এর বুঝতে পারা — নিচে দুজনের machine-ই এক: heap record + fields + code pointers (runtime-systems লেসনের frame/object layout)।
সব paradigm একই silicon-এ নামে
এই module-এর সবচেয়ে গুরুত্বপূর্ণ unifying সত্য — memory-models লেসনের প্রথম ছবিটা ফেরত আনুন: C, Go, JVM, CPython — চারটাই একই virtual pages, একই registers, একই instructions। Paradigm বাস করে source level-এ, আর compiler/runtime-এর কাজ সেই উঁচু ভাষাকে একই নিচু ভাষায় নামানো:
FP-র map/filter → loop + index math (inlined)
OO-র dispatch → vtable lookup: load ptr → load slot → indirect call
Logic-র search → explicit stack + continuation (WAM-স্টাইল registers)
Laziness → heap Thunk + force check (evaluation-strategies build!)Prolog-এর backtracking যখন “automagic” লাগে, মনে রাখুন — Warren Abstract Machine ঠিক আমাদের constraint-solver-এর মতোই একটা explicit stack machine, শুধু ৩৫ বছরের optimization জমা। Magic কোথাও নেই; শুধু কারো হাতে-লেখা machinery আপনার চোখের আড়ালে।
Pattern matching — সব ফ্রেমে ঢুকে পড়া ভাবনা
ML/Haskell-এর pattern matching, Rust-এর match, Python-এর match/case
(3.10+) — সবাই ভেতরে একই যন্ত্রে নামে: patterns-কে decision tree-তে compile
করে, common tests factor করে। Unification-এর সঙ্গে সম্পর্কটাও এখানেই —
pattern match হলো one-way unification (pattern-এর variable bind হয়, ডেটার
structure যাচাই হয়), Prolog-এর টা two-way। Type-systems লেসনের sum-type/
exhaustiveness discussion এখানে এসে মেলে: pattern matching sum type-এর
স্বাভাবিক consumption grammar।
উদাহরণ
Prolog mini-tour — একটা query-র সম্পূর্ণ search tree
% family.pl — facts আর একটা rule
parent(alice, bob).
parent(bob, carol).
parent(bob, dave).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
?- grandparent(Who, carol).Query-টা মানে: কার grandchild carol? Engine চলে unification + depth-first backtracking দিয়ে:
goal: grandparent(Who, carol)
└─ rule unfold: parent(X, Y) ∧ parent(Y, carol) [X,Y unbound]
│
├─ fact ১: parent(alice, bob) → X=alice, Y=bob ✓
│ └─ sub-goal: parent(bob, carol)?
│ └─ fact ২ match ✓ → Who = alice ● প্রথম উত্তর
├─ fact ২: parent(bob, carol) → X=bob, Y=carol ✓
│ └─ sub-goal: parent(carol, carol)? ✗ কোনো fact নেই
│ → FAIL → backtrack (X,Y unbind → আগের choice point)
└─ fact ৩: parent(bob, dave) → X=bob, Y=dave ✓
└─ sub-goal: parent(dave, carol)? ✗ → FAIL → backtrack
আর choice নেই → মোট উত্তর: Who = aliceতিনটা observation — paradigm-এর পুরো স্বাদ এখানেই:
- Program-টাই specification: আমরা লিখেছি “grandparent মানে কী”, খোঁজার কৌশল এক শব্দও নেই — control পুরোটা engine-এর (Kowalski)।
- Unification = bidirectional binding:
parent(X, Y)fact থেকে X/Y পেয়েছে,parent(Y, carol)সেই Y যাচাই করেছে — variable শুধু ভরা হয় না, মেলানো হয়। - Backtracking automatic: ব্যর্থ শাখা থেকে পেছনে গিয়ে পরের বিকল্প — ঠিক
যেন runtime-ই আমাদের আজকের build-এর
backtrack()চালাচ্ছে।
বাস্তব জগতে এই ফ্রেম টয় না: Datalog (Prolog-এর finite subset) চলে static analysis-এ (Soufflé pointer analysis), network policy verification-এ, database query engines-এ (Datomic), security policy languages-তে — যেখানে সমস্যাটা স্বাভাবিকভাবেই “relation + query”, function না।
ছবিতে
চার ফ্রেম, এক মানচিত্র
"কী বলা হয়" ◄──────────────► "কীভাবে চলে"
spec-heavy ◄─ LOGIC ──────────────────── FUNCTIONAL ─► transformation
(facts+rules, (pure values,
engine searches) composition)
│ │
│ দুজনেই state-কে সন্দেহ করে │
▼ ▼
OBJECT-ORIENTED ───────────── IMPERATIVE ► command
(state encapsulated, (state explicit,
messages between) control flow হাতে)
"কে state দেখে" অক্ষটাও পড়ুন — উপরের দুই ফ্রেম
বলে "যতটা পারো কম", নিচের দুই ফ্রেম বলে "ঠিক ঠাকুরে রাখো"problem: "৩ সন্তানের মধ্যে বৈধ বার্তা-ক্রম বের করো"
LOGIC lens : বৈধতার rules লিখো → সব ক্রম enumerate হয়ে যাবে
FUNCTIONAL lens : state → state transition function compose করো
OO lens : Protocol object — প্রতি participant এক actor
IMPERATIVE lens : nested loop + flags + manual stack
(সবচেয়ে দ্রুত, সবচেয়ে ঝুঁকিপূর্ণ)নিজে চালিয়ে দেখুন
N-Queens ৪×৪ — এক সমস্যা, চার ফ্রেম
চার implementation, একই contract: 4×4 board-এ ৪ queen এমনভাবে যেন কেউ কাউকে attack না করে।
১. Imperative — explicit stack, হাতে backtracking:
def queens_imperative(n=4):
solutions = []
board = [-1] * n # board[r] = চলমান trial column
row = 0
while 0 <= row < n:
found = False
start = board[row] + 1
board[row] = -1
for col in range(start, n):
ok = True
for prev in range(row):
if board[prev] == col or abs(board[prev] - col) == row - prev:
ok = False
break
if ok:
board[row] = col
found = True
break
if found:
row += 1
else:
row -= 1
if row == n:
solutions.append(board.copy())
row -= 1 # শেষ row আবার retry — পরের solution
return solutions২. Functional — spec হিসেবে permutation + predicate:
from itertools import permutations
def queens_functional(n=4):
def safe(cols):
pairs = ((r, k) for r in range(n) for k in range(n) if r < k)
return all(abs(cols[r] - cols[k]) not in (0, abs(r - k))
for r, k in pairs)
return [list(p) for p in filter(safe, permutations(range(n)))]৩. OO — Board নিজের invariant নিজে রক্ষা করে:
class QueensBoard:
def __init__(self, n=4):
self._n = n
self._cols = [] # private state
def can_place(self, col):
r = len(self._cols)
return all(c != col and abs(c - col) != r - pr
for pr, c in enumerate(self._cols))
def solutions(self):
out = []
def walk():
if len(self._cols) == self._n:
out.append(list(self._cols))
return
for col in range(self._n):
if self.can_place(col):
self._cols.append(col) # invariant: can_place-এর ভেতরেই
walk() # বাইরে থেকে state ছোঁয়া অসম্ভব
self._cols.pop()
walk()
return out৪. Logic/search — constraints declare, engine solve করে:
def queens_search(n=4):
def extend(cols):
if len(cols) == n:
return [cols]
return [sol
for col in range(n)
if all(c != col and abs(c - col) != len(cols) - r
for r, c in enumerate(cols))
for sol in extend(cols + [col])]
return extend([])Runner আর measurement:
import time
IMPLS = {
"imperative": queens_imperative,
"functional": queens_functional,
"oo": lambda n=4: QueensBoard(n).solutions(),
"search": queens_search,
}
for name, fn in IMPLS.items():
t0 = time.perf_counter()
reps = 500
for _ in range(reps):
result = fn()
dt = (time.perf_counter() - t0) / reps
print(f"{name:<11} {len(result)} solutions {dt*1e6:8.1f} µs/run")
print(sorted(map(tuple, IMPLS["functional"]())))
print(sorted(map(tuple, IMPLS["imperative"]())))Typical output (এই লেখকের মেশিনে; timing আপনারটায় ভিন্ন হবে):
imperative 2 solutions 47.0 µs/run
functional 2 solutions 68.7 µs/run
oo 2 solutions 88.5 µs/run
search 2 solutions 72.6 µs/run
[(1, 3, 0, 2), (2, 0, 3, 1)]
[(1, 3, 0, 2), (2, 0, 3, 1)]Bug-resistance discussion — আসল ফল:
- Imperative: সবচেয়ে দ্রুত, সবচেয়ে ঝুঁকিতে —
board[row] = -1restore, backtrack-এstartcalculation, boundary conditions: তিনটা আলাদা জায়গায় হাতে সিঙ্ক রাখতে হয়। আমার নিজের প্রথম draft-এই backtrack-এ পুরনো column retry ভুল ছিল — এই ফ্রেমে এমন bug স্বাভাবিক। - Functional: ভুলের জায়গা এক —
safepredicate। Spec সঠিক হলে enumeration ভুল হতে পারে না (engine-এর বিশ্বাসযোগ্য)। দাম: সব permutation generate — বড় n-এ অকল্পনীয়। - OO: invariant (
can_place-এর বাইরে placement অসম্ভব) encapsulation-এ বন্দি — ভুল করতে হলে class-এর ভেতরে ঢুকতে হবে। কিন্তু traversal logic তবু হাতে। - Search/logic: formulation ছোট, generalizes (আজকের build দেখুন) — ভুল হলে সেটা constraint-এর অর্থের ভুল, সহজে unit-testable।
যা প্রমাণিত: ফল identical, কিন্তু bug-এর ঠিকানা paradigm-ভেদে সরে — এটাই paradigm নির্বাচনের প্রকৃত criterion: আপনার সমস্যার কোন দিকটা ভুল-করা সবচেয়ে সহজ, সেই দিকটা কোন ফ্রেম ভাষায় অসম্ভব বানায়?
ফল এক (২টা solution), কিন্তু চার ফ্রেমে ভুল-করার সম্ভাবনা ভিন্ন জায়গায় বসে — imperative-এ off-by-one/diagonal-arithmetic, functional-এ spec-এ ভুল (এক জায়গায়), OO-তে state-invariant রক্ষা, logic/search-এ constraint formulation; আর কোডের আকার-আকৃতিই বলে দেয় কোন সমস্যা কোন ফ্রেমের।
নিজে বানান
Generic constraint solver — logic-ফ্রেমকে framework বানানো
- solve(names, domains, constraints) API design করুন — constraint = (scope, predicate)
- Backtracking core লিখুন — প্রতি assignment-এর পরে শুধু affected scope-এর consistency check
- Partial assignment মেনে চলা consistent() লিখুন — যে constraint-এর সব variable assigned, শুধু সেটা যাচাই
- N-Queens instantiate করুন — শুধু pairwise constraints দিয়ে, কোনো search code না
- একই framework-এ graph coloring instantiate করে reuse প্রমাণ করুন
লক্ষ্য: experiment-এর queens_search-কে generalize করা — search machinery
একবার লিখুন, সমস্যা প্রতিটা শুধু facts দিয়ে describe হোক। এটাই logic
paradigm-এর industrial রূপ (Datalog/SAT/CP solver-দের কঙ্কাল)।
# solver.py — generic backtracking constraint solver
def solve(names, domains, constraints):
"""
names : variable-দের ক্রম — এই ক্রমেই assignment হবে
domains : {name: legal values-এর list}
constraints : [(scope, predicate)] — predicate(scoped_dict) -> bool
ফেরত : সব solution-এর generator (each: পূর্ণ assignment dict)
"""
cons = [(frozenset(scope), pred) for scope, pred in constraints]
def consistent(assign):
"""partial assignment-এ যত constraint fully-covered, সব check"""
for scope, pred in cons:
if scope <= assign.keys():
if not pred({k: assign[k] for k in scope}):
return False
return True
def backtrack(i, assign):
if i == len(names):
yield dict(assign)
return
name = names[i]
for value in domains[name]:
assign[name] = value
if consistent(assign):
yield from backtrack(i + 1, assign)
assign.pop(name, None) # parent frame পরিষ্কার রাখো
yield from backtrack(0, {})
# ── Instantiation ১: N-Queens — শুধু constraints, কোনো search কোড না ──
def nqueens(n):
names = [f"Q{r}" for r in range(n)] # Qr = row r-এর queen-column
domains = {q: list(range(n)) for q in names}
constraints = []
for r1 in range(n):
for r2 in range(r1 + 1, n):
a, b = f"Q{r1}", f"Q{r2}"
d = r2 - r1
constraints.append(
((a, b),
lambda s, a=a, b=b, d=d:
abs(s[a] - s[b]) not in (0, d))) # এক কলাম/কর্ণে না
return solve(names, domains, constraints)
# ── Instantiation ২: Graph coloring — একই solver, ভিন্ন facts ──
def coloring(edges, palette):
nodes = sorted({x for e in edges for x in e})
domains = {v: list(palette) for v in nodes}
constraints = [((u, v), lambda s, u=u, v=v: s[u] != s[v])
for u, v in edges]
return solve(nodes, domains, constraints)
if __name__ == "__main__":
sols = list(nqueens(4))
print(f"N-Queens 4x4: {len(sols)} solution")
for s in sols:
print(" ", [s[f"Q{r}"] for r in range(4)])
europe = [("DE", "FR"), ("DE", "PL"), ("FR", "ES"),
("ES", "PT"), ("PL", "UA"), ("AT", "DE")]
first = next(coloring(europe, ["red", "green", "blue"]))
print("graph coloring:", {v: first[v] for v in sorted(first)})প্রত্যাশিত output:
N-Queens 4x4: 2 solution
[1, 3, 0, 2]
[2, 0, 3, 1]
graph coloring: {'AT': 'red', 'DE': 'green', 'ES': 'red', 'FR': 'blue',
'PL': 'red', 'PT': 'green', 'UA': 'green'}যা ঘটল: search machinery (backtrack + consistency) একবার লেখা;
N-Queens আর graph coloring দুটোই শুধু facts — variables, domains, pairwise
rules। Experiment-এর চার ফ্রেমের মধ্যে logic ফ্রেমটাই একমাত্র যার মূল্য
reuse-এ ফোটে: নতুন constraint problem মানে নতুন ৫ লাইন description,
০ লাইন search। (lambda-গুলোর a=a trick খেয়াল করুন — late-binding capture
bug থেকে বাঁচার classic Python idiom; closures লেসনের সরাসরি প্রয়োগ।)
নিজে বাড়ান:
- MRV heuristic যোগ করুন — প্রতি ধাপে সবচেয়ে ছোট domain-এর variable আগে assign; 8-Queens-এ node-count কত কমে মাপুন (count instrumentation দিন)।
- Forward checking যোগ করুন — assignment-এর পরে neighbor-দের domain থেকে illegal values prune; কোন সমস্যায় লাভ বেশি?
- Constraint-এ weight/arithmetic যোগ করুন — optimization সমস্যায় (সব solution না, best solution): branch-and-bound-এর দরজা খুলবে।
- Solver-কে generator রেখে সব solutions lazy consume করুন —
evaluation-strategies লেসনের demand-driven ভাবনাটা এখানে এসে মেলে:
next(solve(...))একটাই solution চাইলে পুরো space ঘোরার দরকার নেই।
বাস্তব সিস্টেমে
চার ফ্রেম production-এ যেখানে বসে
SQL — পৃথিবীর সবচেয়ে বড় declarative victory। আপনি লেখেন “কোন data চাই” (relation!), planner ঠিক করে “কীভাবে আনব” (control)। Query optimizer-এর অস্তিত্বই সম্ভব কারণ SQL logic-আর-control আলাদা করে — Kowalski-র থিসিসের trillion-dollar বাস্তবায়ন।
Datalog পুনর্জাগরণ। Static analysis (Soufflé — pointer analysis, security audit tools), network configuration verification (AWS-এর Zelkova policy checks), database (Datomic), knowledge graphs — যেখানে সমস্যা relation-shaped, সেখানেই logic ফ্রেম ফিরে আসছে।
Excel — বিশ্বের বৃহত্তম functional program। প্রতিটা cell এক pure formula, dependency graph-এ demand-driven recalculation — mutation নেই, event loop নেই, race নেই। কোটি user-এর হাতে functional thinking, নাম না জানিয়েই।
Erlang/Elixir আর OTP। Actor + supervision — telecom-এ জন্ম, chat/fintech- এ রাজত্ব। OO-র message-passing heritage-এর সবচেয়ে committed বংশধর।
Rust — সচেতন multi-paradigm-এর textbook। Ownership (imperative discipline), traits+iterators (functional), pattern matching (ML), channels (CSP library), zero-cost abstractions (systems imperative) — প্রতিটা ফ্রেম থেকে সেরাটা, restriction-গুলো type-system-এ জোর করে।
Airline crew scheduling, chip placement, exam timetabling। Constraint programming-এর ঘরানা — আজকের build-এর industrial ভাইয়েরা; সবার মূল একই: facts describe, engine search।
যে ভুলগুলো সবাই করে
“Paradigm মানে coding style — একই কাজ যে কোনো paradigm-এ একই রকম লেখা যায়।”
Style হলো surface; paradigm হলো semantic commitment — কী first-class, কী নিষিদ্ধ, কী guarantee। Haskell-এ unrestricted mutation নিষিদ্ধ মানে আপনার program-এর সম্পর্কে কিছু প্রমাণ করা যায় যা Python-এ কখনোই না (equational reasoning)। উল্টোটাও সত্য: C-তে memory layout control guarantee আছে যা Haskell দেয় না। ভাষা বদল মানে promise-sheet বদল, font বদল না।
“Functional programming মানে production-এ mutation সম্পূর্ণ নিষিদ্ধ।”
Practical FP mutation নিয়ন্ত্রণ করে, নির্মূল না। Haskell-এর ST monad
local mutable refs দেয় pure interface-এর পেছনে; Clojure-র transients; Erlang-
এ per-process state আসলে mutable — শুধু সীমানা immutable। নীতিটা: mutation
যেখানে দরকার, সেখানেই — কিন্তু ছোট, সীমান্ত-চিহ্নিত, আর type system-এ
documented। “Zero mutation” purity হলো teaching ideal, engineering target না।
“OO মানে inheritance hierarchy — class ছাড়া OO না।”
Smalltalk — OO-র জনক-ভাষা — single inheritance, মূল দর্শন message-passing আর encapsulation। Modern consensus আরও দূরে: composition-over-inheritance, interfaces/protocols (Go-র structural typing, Rust-এর traits — inheritance নেই-ই), data-oriented designs। OO-র অপরিহার্য core দুটো: state লুকানো + behavior-এর সাথে বাঁধা, আর polymorphic dispatch। Hierarchy একটা (বর্তমানে সন্দেহভাজন) tool মাত্র।
“Logic programming academic toy — industry-তে Prolog লেখে কেউ না।”
Direct Prolog কম, কিন্তু logic-ফ্রেমের বংশধররা সর্বত্র: SQL (relation + optimizer), Datalog-ভিত্তিক static analyzers (Soufflé, CodeQL-এর ভিত), AWS policy verification (Zelkova), Datomic/TerminusDB, SAT/CP solvers — chip EDA থেকে crew scheduling। আর regex engine থেকে build system dependency resolution — backtracking আপনার প্রতিদিনের tooling-এই চলে, নাম না ধরে।
বুঝেছেন কি না দেখুন
1Referential transparency-র সংজ্ঞা দিন, আর এ থেকে ঝরে আসা তিনটা সুফলের
প্রতিটার এক-লাইন justification লিখুন।
স্মরণ
সংজ্ঞা: যেকোনো expression তার মান দিয়ে replace করা যায় প্রোগ্রামের অর্থ অক্ষত রেখে (কোনো hidden state/effect না)। সুফল: (১) Equational reasoning — replace-property মানেই algebraic law দিয়ে refactor/proof; (২) Memoization — এক input সবসময় এক output, তাই caching semantically safe (thunk-মemoization); (৩) Free parallelism — ফল অন্য কারো উপর নির্ভরশীল না হলে evaluation order মুক্ত, তাই যেকোনো সমান্তরাল ভাগ সঠিক।
2total = 0; for x in items: total += price(x) — এই imperative loop-কে
functional ফ্রেমে rewrite করুন, আর বলুন নতুন রূপটা কেন memoizable/composable
কিন্তু পুরনোটা না।
প্রয়োগ
total = 0; for x in items: total += price(x) — এই imperative loop-কে
functional ফ্রেমে rewrite করুন, আর বলুন নতুন রূপটা কেন memoizable/composable
কিন্তু পুরনোটা না।total = sum(map(price, items)) (বা sum(price(x) for x in items))। নতুন
রূপটা expression — items fixed থাকলে ফল constant, তাই cache-safe; আর
composition-এর জন্য খোলা: sum(map(price, filter(in_stock, items))) —
intermediate state-এর কোনো bookkeeping নেই। Loop-সংস্করণে total mutable
cell — কোন ধাপে কী হলো তা history-dependent, তাই একই “call” ভিন্ন context-
এ ভিন্ন মান দিতে পারে: memoization আর equational replacement দুটোই unsafe।
3“Closure আর object একই জিনিসের দুই পোশাক” — hood-এর counter উদাহরণ ধরে
ব্যাখ্যা করুন। কোন সমস্যায় closure-form বেশি expressive, কোনটায় object-form?
যুক্তি
দুটোই heap-record + captured/instance state + associated code — state ছাড়া access নেই, access ছাড়া state অর্থহীন। Closure-form জেতে যখন এক operation আর lexical scoping যথেষ্ট (callbacks, strategies, memoizers — Python-এর decorator, JS-এর module pattern): কম ceremony, state structurally invisible। Object-form জেতে যখন একগুচ্ছ operation একই state শেয়ার করে + identity দরকার (comparison, reflection, subclassing): multiple dispatch entry, interface conformance, tooling visibility। মূল প্রশ্ন: operation-সংখ্যা আর identity-দরকার দেখে পোশাক বাছুন।
4Problem: বিমানের crew rostering — ২০০ crew, ৯০ দিনের schedule, union rules
(সর্বোচ্চ ঘণ্টা, বিশ্রাম-ব্যবধান), qualification matrix, cost minimization।
কোন paradigm(-combination) বেছে নেবেন এবং কেন? Imperative-only approach-এর
বিপদ কী?
ডিজাইন
Core: constraint/optimization ফ্রেম (আজকের solver-এর industrial রূপ — CP/ MIP solver) — rules হলো constraints, cost objective, engine search (branch- and-bound/LP relaxation)। কারণ: সমস্যাটা relation+rule-shaped, আর rules প্রায়ই বদলায় (union negotiation) — declarative description মানে rule-change মানেই data-change, search-code touch না। সহায়ক স্তর: data pipeline functional (transform rosters immutably), reporting OO/imperative যেখানে সস্তা। Imperative-only বিপদ: search strategy আর business rules একই কোডে গাঁথা হয়ে যায় — প্রতিটা rule-tweak এখন heuristic-code surgery; correctness reasoning (completeness, optimality gap) অসম্ভব; আর এত বড় space-এ naive hand-search performance-এও ডুববে।
5concurrency-models লেসনের তুলনা-ছকে আমরা দেখেছিলাম shared-state threads-এর
রোগ race/deadlock। Immutable-first paradigm এই রোগগুলোর কোনটা structural
ভাবে অসম্ভব করে, কোনটা শুধু কঠিন করে? Persistent data structure-এর ভূমিকা কী?
যুক্তি
Structural অসম্ভব: lost update আর data race — কোনো thread অন্যের পড়া ডেটা mutate-ই করতে পারে না, তাই interleaving যা-ই হোক ফল deterministic। Lock-order deadlock-ও মূলত অদৃশ্য (lock-ই দরকার নেই read-path-এ)। কঠিন হয়ে যায়, অসম্ভব হয় না: logical staleness bugs (পুরনো snapshot-এ সিদ্ধান্ত), আর coordination bugs (কে কখন নতুন version publish করবে — CAS/retry loops)। Persistent data structures (structurally sharing, update-এ নতুন version) এখানে bridge: mutation- স্টাইল ergonomics + immutability guarantees — Clojure/Haskell-এর ভিত; functional- ফ্রেমকে concurrent workload-এ practical বানায় ঠিক এই খরচ-model-এ (O(log n) copy)।
এরপর কী
এরপর কী — module-এর সমাপ্তি
পনেরো লেসনের যাত্রা শেষ হলো। ফেরত দেখুন পথটা: syntax আর semantics দিয়ে শুরু — লেখা আর অর্থ আলাদা জিনিস; formal-semantics-এ অর্থকে যন্ত্রের নিয়মে বাঁধলেন; type-systems, Hindley-Milner, polymorphism — ভুল আগে ধরার গণিত; memory-models, garbage-collection, reference-counting — ডেটার জীবন-মৃত্যুর চার রাজনীতি; runtime-systems — main-এর আগে-পরে যা চলে; closures — ফাংশন যখন environment নিয়ে হাঁটে; evaluation-strategies — গণনা কখন হয়; exception internals — control কীভাবে উল্টোপথে পালায়; concurrency-models — অনেক কাজের চার স্থাপত্য; FFI/ABI — ভাষাদের মধ্যের নীরব চুক্তি; আর আজ paradigms — চিন্তার চার ফ্রেম, প্রতিটার restriction আর guarantee।
এই পুরো জ্ঞানের সবচেয়ে সুন্দর পরীক্ষা এখন সামনে: compilers module — Level 5-এর এই ধারাবাহিকতারই পরবর্তী ধাপ। এতদিন আপনি ভাষার অর্থ পড়তে শিখেছেন; compilers শেখাবে সেই অর্থ silicon-এ অনুবাদ কীভাবে হয় — lexer যেভাবে text-কে token-এ ভাঙে, parser যেভাবে grammar-এ AST গড়ে (abstract-syntax-tree গ্লসারি টার্মটা এখনই চেনা লাগবে), type-checker আর optimizer কীভাবে আপনার চেনা semantic নিয়মগুলোকে ব্যবহার করে, আর codegen কীভাবে সব কিছু নামিয়ে আনে assembly module-এর সেই instruction-গুলোতে। আপনার নিজের হাতে লেখা evaluator, scheduler, unwinder, FFI wrapper — সবগুলোই তখন compiler-এর ভেতরের যন্ত্রাংশ হয়ে ফিরে আসবে।
তারপর Level 6 — algorithms আর তার বাইরে: যে ভাষা আপনি এখন ভেতর থেকে বোঝেন, তাতে কী লিখলে কত খরচ — সেই প্রশ্নের উত্তর এখন আর বিশ্বাসের বিষয় না, আপনার কাছে সেটা মাপার যন্ত্র। এই module-এর প্রতিটা build আপনার নিজের হাতের লেখা প্রমাণ।
আরও পড়ুন
- Structure and Interpretation of Computer Programs — Harold Abelson, Gerald Jay Sussman, Julie Sussman · 'Languages as frameworks for thinking' — এই লেসনের দর্শনের মূল উৎস
- Types and Programming Languages — Benjamin C. Pierce · Type system-কে paradigm-হিসেবে পড়ার formal ভিত্তি — type-systems লেসনের ধারাবাহিকতা
- Algorithm = Logic + Control (CACM 1979) — Robert Kowalski · Logic programming-এর কেন্দ্রীয় থিসিস — যা লিখছ আর কীভাবে চলছে আলাদা
- Programming Language Pragmatics — Programming Paradigms chapters — Michael L. Scott · চার paradigm-এর ভাষা-ভেদে comparative survey