Short notes on function closure
The concept of `closure` may seem deceivingly easy to grasp – just a function that returns another function which could access the parent function’s environment.
The concept was easy enough to grasp, but I really didn’t get it to know its importance. So last weekend I’ve spent some time reading about it (also because rumours says one can get a senior developer job if he or she understood the concept really well), and here’s what I’ve learned.
The magic of closure really is this: the scope of the returned function contains the scope of the enclosing function. The key is this: the parent function does not play any role here any more. All the variables referenced by the inner function “belong to” the inner function, no middleman involved at all. Obviously, unused reference are garbage collected to avoid memory leakage. Once I got this, everything onward made sense.
This does require a language to have:
- First-class functions
- lexical scoping (most languages), rather than dynamic scoping (e.g. Emacs Lisp!)
Applications of closure
- Module pattern in JS talked about immediately invoked function expressions (IIFEs), global import and module export. I think the ubiquitous use of closure was probably evolved under the pressure from the JS itself, a language without native support for modules
- Functional programming: curry, partials, memoizations
- Asynchronous programming: callback functions for web development and timers etc.
- Generators: good old counter example without the need of a global variable to keep track
Why didn’t I need to know this for Python?
- Actually, decorators make heavy use of closures already
- However, closures are not as popular as in JS because Python has proper `Class` and `methods`, which provide most of the benefit of closure. Also closures can be slightly harder to read, so not the most pythonic in terms of explicitness
- Closures in Python are not as powerful as JS, at least until Py3. In Py2, you will get an `UnboundLocalError` exception because the referenced variables are read-only. Since Python 3, writeable closure can be achieved with `nonlocal`
def counter(): i = 0 def f(): i += 1 return i return f counter()() # > UnboundLocalError: local variable 'i' referenced before assignment
- Python has built-in `yield` keyword for generators, which replaces much of the need for closures
To close ;-)
Closure is a simple but important concept. The key is to understand that the returned function references the outer scope variables directly. This makes closures a very lightweight and useful construct.