Instances of classes and closures feel like 2 competing approaches for certain problems. Instances of classes have state and behavior, but that behaviour is normally splat in multiple execution units (methods). A closure is a single execution unit (a function) that keeps state through the variables it traps (freevars). When a class has a single method, you can model it as a closure (well, a closure factory, so that each closure instance has its own state). Additionally, languages like Python have callable classes, where you have a main/default execution unit (__call__), so they feel closer to a closure :-)
Somehow the other day I realised that in languages like Python or JavaScript, methods of a class can be clousures. How? Well, in Python classes are objects (in JavaScript a class is just syntax sugar for managing functions and prototypes), and we can define classes inside functions, so each time the function runs a new class is created (and returned, so the function becomes a class factory). What happens if a method in one of these internal classes tries to access to a variable defined at the outer function level? Well, it will trap it in its closure. Let's see an example where the format method has access to the pre_prefix variable from the enclosing function.:
def formatter_class_factory(pre_prefix):
class Formatter:
def __init__(self, prefix):
self.prefix = prefix
def format(self, tx):
# this method is accessing the pre_prefix variable from the enclosing scope
return f"{pre_prefix} {self.prefix}: {tx}"
return Formatter
MyFormatter = formatter_class_factory("Log")
formatter = MyFormatter("INFO")
print(formatter.format("This is a test message."))
print(f"closure: {MyFormatter.format.__closure__}")
print(f"freevars: {MyFormatter.format.__code__.co_freevars}")
print(f"closure[0].cell_contents: {MyFormatter.format.__closure__[0].cell_contents}")
# Log INFO: This is a test message.
# closure: (| ,)
# freevars: ('pre_prefix',)
# closure[0].cell_contents: Log
|
We can write the equivalent JavaScript code and see that it works the same. So JavaScript methods can also get access to variables present in the scope where the class is defined. What this means is that same as regular functions, methods also have a scope-chain (where the freevars will be looked up).
function formatterClassFactory(prePrefix) {
class Formatter {
constructor(prefix) {
this.prefix = prefix;
}
format(tx) {
// this method is accessing the prePrefix variable from the enclosing scope
return `${prePrefix} ${this.prefix}: ${tx}`;
}
}
return Formatter;
}
const MyFormatter = formatterClassFactory("Log");
const formatter = new MyFormatter("INFO");
console.log(formatter.format("This is a test message."));
// Log INFO: This is a test message.
// Notice that JavaScript does not provide direct closure introspection (no equivalent to __closure__), so we can not translate that part from the Python snippet
By the way, it's interesting how for people coming from class based languages the idea that "closures are poor man's class instances" makes sense, while for people coming from functional languages "class instances are poor man's closures". This is discussed here.
No comments:
Post a Comment