Closures මොනවද? JavaScript/Python වල Closures තේරුම් ගනිමු - SC Guide

මචංලා, සොෆ්ට්වෙයා ඩෙවලොප්මන්ට් පැත්තට ආවහම, සමහර වෙලාවට අපිට ටිකක් අමුතු, එකපාරට තේරුම් ගන්න අමාරු concepts හම්බවෙනවා නේද? ඒ වගේ එකක් තමයි මේ Closures කියන්නේ. මුලින්ම ඇහුවම 'මොකක්ද බන් මේක?' කියලා හිතෙන්න පුළුවන්. ඒත් මගේ යාළුවනේ, මේක ගොඩක්ම Powerful වගේම, හරියට තේරුම් ගත්තොත්, ඔයාලගේ code එක ගොඩක් clean, efficient සහ maintainable කරන්න පුළුවන් tool එකක්.
අද අපි මේ article එකෙන් Closures කියන්නේ මොනවද, ඒවා කොහොමද වැඩ කරන්නේ, practical විදියට ඒවා පාවිච්චි කරන්නේ කොහොමද, ඒ වගේම ඒවගේ තියෙන පොඩි පොඩි ගැටළු සහ ඒවා use කරන Best Practices මොනවද කියන එක ගැන සරලව, සිංහලෙන් කතා කරමු. මොකද, මේක JavaScript, Python වගේ ගොඩක් Programming Languages වලට පොදු Concept එකක්. එහෙනම් අපි පටන් ගමු!
Closures කියන්නේ මොනවද? (Theory Part)
සරලවම කිව්වොත්, Closure එකක් කියන්නේ තමන් නිර්මාණය වුණ Scope එකේ (environment) තියෙන Variables මතක තියාගන්න පුළුවන් Function එකක්. 'මතක තියාගන්නවා' කියන්නේ, ඒ function එක return කරාට පස්සේ හෝ, ඒ function එක නිර්මාණය කරපු Parent function එක execute වෙලා ඉවර වුණාට පස්සෙත්, ඒ Parent function එකේ තිබ්බ Variables වලට access කරන්න පුළුවන්කම තියෙන එක.
Nested Functions සහ Enclosing Scope
Closure එකක් හදන්න ප්රධානම දේ තමයි Nested Function එකක්. ඒ කියන්නේ, function එකක් ඇතුළේ තව function එකක් හදන එක. මේ ඇතුළේ තියෙන function එකට කියනවා Inner Function කියලා, ඒක ඇතුළේ තියෙන function එකට කියනවා Outer Function කියලා.
සාමාන්යයෙන් Programming Languages වල Scope කියන concept එක ගොඩක් වැදගත්. Scope එකක් කියන්නේ Variable එකක් access කරන්න පුළුවන් සීමාව. Closure එකක් වැඩ කරන්නේ මේ Scope rules වලට අනුව තමයි. Inner function එකට තමන්ගේ Scope එකේ තියෙන Variables වලට අමතරව, Outer Function එකේ Scope එකේ තියෙන Variables වලටත් access කරන්න පුළුවන්.
අපි මේකට Simple Example එකක් බලමු. මුලින්ම JavaScript වලින්:
function outerFunction(outerVariable) {
// outerFunction's scope. outerVariable is defined here.
function innerFunction(innerVariable) {
// innerFunction's scope. innerVariable is defined here.
// It can also access outerVariable from outerFunction's scope.
console.log('Outer Variable:', outerVariable);
console.log('Inner Variable:', innerVariable);
}
return innerFunction; // innerFunction is returned, creating a closure
}
const myFunction = outerFunction('Hello'); // outerVariable is 'Hello'
myFunction('World'); // innerFunction is called here. outerVariable 'Hello' is still remembered.
// Expected output:
// Outer Variable: Hello
// Inner Variable: World
ඔයාලට පේනවා ඇති, outerFunction
එක execute වෙලා ඉවර වුණාට පස්සෙත් (myFunction = outerFunction('Hello')
line එකෙන් පස්සේ), innerFunction
එක (දැන් myFunction
විදියට save වෙලා තියෙන්නේ) තවදුරටත් outerVariable
කියන එකේ අගය ('Hello') මතක තියාගෙන ඉන්නවා. මේක තමයි Closure එකක් කියන්නේ!
Python වලත් මේක හරියටම මේ විදියටම වැඩ කරනවා:
def outer_function(outer_variable):
# outer_function's scope. outer_variable is defined here.
def inner_function(inner_variable):
# inner_function's scope. inner_variable is defined here.
# It can also access outer_variable from outer_function's scope.
print('Outer Variable:', outer_variable)
print('Inner Variable:', inner_variable)
return inner_function # inner_function is returned, creating a closure
my_function = outer_function('Hola') # outer_variable is 'Hola'
my_function('Amigos') # inner_function is called here. outer_variable 'Hola' is still remembered.
# Expected output:
# Outer Variable: Hola
# Inner Variable: Amigos
සරලව කියනවා නම්, Inner Function එකක් තමන්ගේ Parent Function එකේ Scope එකේ තියෙන Variables වලට reference එකක් තියාගෙන ඉන්නවා. ඒ Parent Function එකේ execution එක ඉවර වුණාට පස්සෙත්, ඒ reference එක maintain කරනවා. මේක හරියට, ඔයා කොහේ ගියත්, ඔයාගේ ගෙදර තිබ්බ දේවල් ගැන මතකයක් තියාගෙන ඉන්නවා වගේ වැඩක්.
Practicalව Closure එකක් හදාගමු! (Practice)
Closures වල තියෙන ලොකුම වාසියක් තමයි, ඒවා State එකක් Maintain කරන්න පාවිච්චි කරන්න පුළුවන් එක. ඒ කියන්නේ, Function එකක Private Variable එකක් වගේ දෙයක් හදන්න පුළුවන්. අපි මේකට ගොඩක්ම සුලබ උදාහරණයක් වන 'Counter' එකක් හදලා බලමු. අපිට අවශ්යයි, එක Counter එකක්, ඒක call කරන හැම පාරම එකකින් වැඩි වෙන්න. ඒ වගේම, Counter කිහිපයක් හදන්න පුළුවන් වෙන්න ඕනේ, හැම Counter එකක්ම තමන්ගේම ගණන වෙන වෙනම තියාගන්න ඕනේ.
Function Factory එකක් විදියට
අපි මේකට Closure එකක් හදමු, ඒකෙන් තව function එකක් return කරනවා. මේ වගේ function එකකට කියනවා Function Factory එකක් කියලා.
JavaScript වලින්
function createCounter() {
let count = 0; // This is the 'closed-over' variable. It's private to each counter instance.
// The inner function that will be returned.
return function() {
count++; // This function 'remembers' and modifies its own 'count'.
console.log(count);
};
}
// Counter instances created using the factory.
const counter1 = createCounter(); // counter1 gets its own 'count' variable
const counter2 = createCounter(); // counter2 gets its own separate 'count' variable
console.log('--- Counter 1 ---');
counter1(); // Output: 1 (count for counter1 is 1)
counter1(); // Output: 2 (count for counter1 is 2)
counter1(); // Output: 3 (count for counter1 is 3)
console.log('--- Counter 2 ---');
counter2(); // Output: 1 (count for counter2 is 1)
counter2(); // Output: 2 (count for counter2 is 2)
console.log('--- Counter 1 (again) ---');
counter1(); // Output: 4 (count for counter1 continues from where it left off)
මෙහිදී createCounter
කියන Outer Function එක call කරන හැම පාරම, ඒකට අලුත් count
Variable එකක් හදනවා. ඒ count
Variable එක අලුතින් return කරන Inner Function එකට (Anonymous function එක) 'close over' වෙනවා. ඒ නිසා, counter1
කියන එකට තමන්ගේම count
එකක් තියෙනවා, counter2
කියන එකට තමන්ගේම count
එකක් තියෙනවා. මේවා එකිනෙකට බලපාන්නේ නෑ.
Python වලින්
def create_counter():
count = 0 # This is the 'closed-over' variable. It's private to each counter instance.
# The inner function that will be returned.
def increment():
nonlocal count # This keyword is necessary in Python to modify 'count' from the outer scope
count += 1 # This function 'remembers' and modifies its own 'count'.
print(count)
return increment
# Counter instances created using the factory.
counter1 = create_counter() # counter1 gets its own 'count' variable
counter2 = create_counter() # counter2 gets its own separate 'count' variable
print('--- Counter 1 ---')
counter1() # Output: 1
counter1() # Output: 2
counter1() # Output: 3
print('--- Counter 2 ---')
counter2() # Output: 1
counter2() # Output: 2
print('--- Counter 1 (again) ---')
counter1() # Output: 4
Python වලදී, Inner Function එකක් ඇතුළේ Outer Scope එකේ තියෙන Variable එකක් modify කරනවා නම්, nonlocal
කියන keyword එක පාවිච්චි කරන්න ඕනේ. එහෙම නැතුව direct assign කරන්න ගියොත්, Python හිතනවා ඒක Inner Function එකේ අලුත් Local Variable එකක් කියලා. මේක Python වල Closures වලදී පොඩ්ඩක් සැලකිලිමත් වෙන්න ඕනේ තැනක්.
"Free Variables" කියන්නේ මොනවද? (Review & Troubleshooting)
Closure එකකදී Inner Function එකක් විසින් access කරන, ඒ වගේම ඒ Inner Function එකේ Local Scope එකේ හෝ Arguments විදියට Define කරලා නැති Variables වලට තමයි "Free Variables" කියලා කියන්නේ. උඩ තියෙන Counter example එකේදී, count
කියන Variable එක Free Variable එකක්.
Closures හරියට තේරුම් ගන්න මේ "Free Variables" concept එක ගොඩක් වැදගත්. Inner Function එකක් return වුණාම, ඒක මේ Free Variables වලට reference එකක් තියාගෙන ඉන්නවා. ඒක copy එකක් නෙවෙයි, direct reference එකක්. ඒ කියන්නේ, ඒ Variable එක modify වුණොත්, Inner Function එකට ඒ modified value එක පේනවා.
Troubleshooting Points:
- Variable Capture Time: Free Variables capture වෙන්නේ Inner Function එක define වෙන වෙලාවේදී, call වෙන වෙලාවේදී නෙවෙයි. මේක සමහර වෙලාවට පොඩි confuse වීම් ඇති කරන්න පුළුවන්, විශේෂයෙන් Loops ඇතුළේ Closures හදනකොට.
- Mutable vs. Immutable (Python): Python වලදී numbers, strings, tuples වගේ immutable data types Free Variables විදියට ගත්තම, ඒවා Inner Function එක ඇතුළෙන් modify කරන්න බෑ (
nonlocal
පාවිච්චි නොකර). List, dictionaries වගේ mutable data types modify කරන්න පුළුවන්. JavaScript වලට මේ ගැටලුව නෑ, මොකද ඒකේ Variables `let`, `const`, `var` වලින් define කරන විදිය මත Scope එක වෙනස් වෙනවා.
උදාහරණයක් විදියට මේ JavaScript code එක බලන්න. මේක Closure එකක් හරියට පාවිච්චි නොකරන නිසා වෙන පොඩි ගැටලුවක්.
function createFunctions() {
const functions = [];
for (var i = 0; i < 3; i++) {
functions.push(function() {
console.log(i);
});
}
return functions;
}
const myFunctions = createFunctions();
myFunctions[0](); // Output: 3
myFunctions[1](); // Output: 3
myFunctions[2](); // Output: 3
// Expected output: 0, 1, 2. Why 3?
මේක වෙන්නේ var
කියන එක function-scoped නිසා. Loop එක ඉවර වෙනකොට i
වල අගය 3
වෙලා තියෙනවා. හැම Inner Function එකක්ම ඒ i
වලට තියෙන reference එක තමයි මතක තියාගෙන ඉන්නේ. ඒ නිසා, functions execute වෙනකොට, i
වල අවසාන අගය (3
) තමයි ඒ හැමෝටම පේන්නේ.
මේකට විසඳුම තමයි Closure එකක් හරියටම පාවිච්චි කරන එක. let
කියන එක block-scoped නිසා, හැම iteration එකකටම අලුත් i
value එකක් create වෙනවා, ඒ නිසා Inner Function එකට ඒක මතක තියාගන්න පුළුවන්.
function createFunctionsCorrectly() {
const functions = [];
for (let i = 0; i < 3; i++) { // Changed 'var' to 'let'
functions.push(function() {
console.log(i); // Each function closes over its specific 'i' from that iteration
});
}
return functions;
}
const myFunctionsCorrectly = createFunctionsCorrectly();
myFunctionsCorrectly[0](); // Output: 0
myFunctionsCorrectly[1](); // Output: 1
myFunctionsCorrectly[2](); // Output: 2
Best Practices & Decorators වලට පදනම (Advanced Concepts)
Closures පාවිච්චි කරන්න පුළුවන් තැන් ගොඩක් තියෙනවා. මේවා හරිම ප්රයෝජනවත්, විශේෂයෙන් Data Encapsulation, State Management, සහ Functional Programming patterns වලදී.
ප්රධාන භාවිතයන්:
- Memoization: ගණනය කරන්න වෙලාව යන function වල results cache කරලා තියාගන්න. ඒකත් Closures පාවිච්චි කරලා කරන්න පුළුවන්.
Decorators (Python වල): Python වල Decorators කියන්නේ Closures වල තියෙන ලොකුම use case එකක්. Decorator එකක් කියන්නේ, තව Function එකක් Argument එකක් විදියට අරගෙන, ඒක modify කරලා (උදා: logging, timing, authentication වගේ දේවල් එකතු කරලා) අලුත් Function එකක් return කරන Function එකක්. මේකෙන් Code Reusability සහ Readability වැඩි වෙනවා.
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs) # Call the original function
end_time = time.time()
print(f"Function {func.__name__} ran in {end_time - start_time:.4f} seconds")
return result
return wrapper # wrapper closes over 'func'
@timer # This is syntactic sugar for long_running_function = timer(long_running_function)
def long_running_function():
time.sleep(2) # Simulate a long operation
print("Function finished its work.")
print("Calling long_running_function...")
long_running_function()
print("Finished calling long_running_function.")
මේ example එකේදී timer
කියන Decorator එක long_running_function
එක Argument එකක් විදියට ගන්නවා. wrapper
කියන Inner Function එක func
(ඒ කියන්නේ long_running_function
) කියන Variable එක මතක තියාගෙන ඉන්නවා. ඒ නිසා, long_running_function
call කරනකොට, ඇත්තටම call වෙන්නේ wrapper
function එක, ඒක long_running_function
එකට කලින් සහ පස්සේ time එක measure කරනවා.
Partial Application / Currying: Function එකකට එකපාරටම හැම argument එකක්ම දෙන්නේ නැතුව, ටිකෙන් ටික arguments දීලා, අන්තිමට final result එක ගන්න පුළුවන් විදියට functions හදන එක.
function multiply(a) {
return function(b) {
return a * b;
};
}
const multiplyByFive = multiply(5);
console.log(multiplyByFive(10)); // 50
console.log(multiplyByFive(3)); // 15
Data Privacy/Encapsulation: Counter example එකේදී වගේ, private variables හදන්න පුළුවන්. count
variable එකට පිටතින් access කරන්න බෑ, ඒක handle කරන්න පුළුවන් return කරන Inner Function එකෙන් විතරයි. මේක OOP (Object-Oriented Programming) වල private methods වගේ Concept එකක් Functional Programming වලදී අනුකරණය කරනවා.
function createPerson(name) {
let age = 0; // Private variable
return {
getName: function() { return name; },
getAge: function() { return age; },
incrementAge: function() { age++; }
};
}
const person1 = createPerson('Nimal');
console.log(person1.getName()); // Nimal
console.log(person1.getAge()); // 0
person1.incrementAge();
console.log(person1.getAge()); // 1
// console.log(person1.age); // Undefined - cannot access directly
දැන් ඔයාලට තේරෙනවා ඇති, Closures කියන්නේ Programming වල තියෙන හරිම Powerful Concept එකක් කියලා. මේවා හරියටම තේරුම් ගත්තොත්, ඔයාලට වඩාත් හොඳ, maintainable Code ලියන්න පුළුවන්.
අවසාන වශයෙන්...
ඉතින් මචංලා, අද අපි Closures කියන Concept එක ගැන මුල ඉඳලම කතා කළා. මුලින් පොඩ්ඩක් අමාරු වගේ පෙනුනත්, මේක තේරුම් ගත්තම ගොඩක්ම ආතල් එකක් වගේම ගොඩක් වැඩ වලට පාවිච්චි කරන්න පුළුවන් දෙයක්. Nested Functions, Enclosing Scope, Free Variables, Function Factories, Decorators වගේ දේවල් ගැන දැන් ඔයාලට හොඳ අවබෝධයක් ඇති කියලා මම හිතනවා.
මේ concepts ටික අතින්ම try කරලා බලන එක ගොඩක් වැදගත්. JavaScript Console එකේ හෝ Python Interactive Shell එකේදී මේ examples ටික Run කරලා බලන්න. පොඩි පොඩි වෙනස්කම් කරලා output එක බලන්න. එතකොට තව දුරටත් මේවා ගැන ගැඹුරු අවබෝධයක් ගන්න පුළුවන්.
ඔයාලගේ අදහස්, ප්රශ්න පහළින් comment කරලා යන්න අමතක කරන්න එපා. මේ Article එක ගැන ඔයාලගේ අදහස මොකක්ද? ඔයාලා Closures පාවිච්චි කරන වෙනත් Use Cases මොනවද? අපි ඊළඟ Article එකෙන් හමුවෙමු!
අදට එහෙනම් අපි ගිහින් එන්නම්! සුබ දවසක්!