Coding Interview — Closures in Details
source link: https://blog.thelonearchitect.com/coding-interview-closures-in-details-5d797f765ad2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Coding Interview — Closures in Details
Closures are part of these concepts we seem to understand without being able to clearly explain them.
Hopefully, there’s nothing magical about them.
It’s a matter of scope
An identifier, or a symbol, is a name inside a program. It can map to either a variable name or a function name. In more elaborate programming languages, it can also correspond to a class name or an interface.
These names have different lifespans :
- Global exist for the existence of the program
- Local exist within a scope
A scope is part of the program where the symbol can be referenced. Outside of this scope, the symbol no longer exist.
Scopes are created automatically at different moments :
- When the program begins, a global scope is created
- Every function creates its own local scope
- In most C-like languages, opening braces create new scopes. Hence, loops, conditionals and raw braces each create a new scope
Simple C code
In this sample C program, there’s 2 scopes in action :
- The global scope where
myGlobal
,myOtherGlobal
andwork
exist until the program exit - The
work
function scope, wherea
,b
andmyLocal
exist until the function returns.
An interesting property of these scopes is how they nest : an enclosed scope always live less than its enclosing scope. This is called lexical scoping.
In this example, the main
method is called first, and when the program starts, it begins by executing main
. Here is how these scopes are created while the program is running :
- The global scope is initialized with global variables and functions
main
is invoked and its scope is created- The
result
variable is created intomain
scope - The
work
function is invoked and its scope is created myLocal
variable is created withinwork
scopework
return and its scope is destroyedmain
return and its scope is destroyed
The scope of work
will be removed before it’s caller, main
, destroys its scope, in a Last-In First-Out (LIFO) manner.
Do you know what data-structure also works in LIFO ? A Stack. And that’s the reason function’s local variables and parameters are allocated on the Stack, because we can leverage this property of scope management to use this area of the program’s memory.
What are closures ?
Before defining a closure, it serves to know they exist under two conditions :
- The language supports nested functions
- The language supports higher-order functions : functions can be passed as arguments and returned from other functions
As a natural conclusion, once these two conditions are met, the language has a very subtle yet important question to solve : what symbols are under the scope of a function ?
Let’s walk through it.
In this JavaScript snippet, it’s pretty straightforward :
- The global scope contains
a
andb
and they are accessible from anywhere a
containslocal
b
containsotherLocal
Hence, inside of a
scope, I have access to :
local
once declaredb
since it’s the global scopea
itself for the same reason and to allow recursion
Now this example is a little bit more complex. All the previous statements remain true, with additional informations :
- Inside
a
, we have access toa
,a2
,local
and alsob
- Inside
b
, we don’t have access toa2
because it exists outside its scope a2
shares the same scope asa
and has no additional identifiers because it declares no variables or sub-nested functions
As you can see, local
is accessible within a2
. However, once a
is done executing, everything is thrown to garbage : nothing from within a
can be accessed from the outside after its execution. So far, we only exhibited nested functions.
Here, we changed b
which now returns a function, a
. Hence, at the end, fnVariable
executes b
and is now referencing a function. We can execute fnVariable()
and it will happily execute the instructions of a
.
Nothing specific here, since b
values are cleared after its execution, and so does a
whose implementation didn’t change. We just exhibited higher-order functions with nested functions, side by side.
Now, what if a function returns a nested function as a higher order function ?
Here be dragons
That’s the beginning of troubles : now, the variable local
outlives the execution of a
because it is referenced inside a2
, the function it returns !
Lexical scopes and Stack memory are helpless in this case. Would the Javascript Engine be using them, once a
return, the value contained inside local
would no longer hold valid data and probably be garbage. Even worse, if new functions are called, it may completely mess the stack and crash the program. That’s a big no-no.
And that’s where Closures come into existence : a closure is a special scope where a nested function retains values of it’s enclosing scope, even after the enclosing function finished its execution.
In this example, the function a2
is returned and keeps a reference to local
, a variable created inside the scope of a
which is supposed to be destroyed once a
return. But because a2
references this variable outside of its own scope, local
must be kept alive somehow.
The JavaScript Compiler (let’s say v8
for the sake of the example) detects closures at compile time, before the program gets executed. It knows that local
will belong to the closure of a2
.
At runtime, each time the function a
is called, a new scope object is created for a2
which holds a reference to local
. In other words, we can’t know in advance how many times a
will be invoked and we can’t prepare the closures before the code is executed. Hence, the closure for a2
is created on heap memory and local
is stored on this heap-allocated closure.
Closures are thus very powerful but consume more memory and are slower to create than regular scopes.
In this very contrived example, a
nests a2
which nests a3
. However, only one function is returned in the end : a3
. And so, only one closure is created, containing value1
and value2
:
value1
is created withina
and outlivesa
because it’s referenced ina3
value2
is created withina2
and outlivesa2
because it’s referenced ina3
Why do we need closures ?
They enable powerful functional-programming patterns
Closures are closely linked to functional-programming paradigms and they enable many powerful patterns. For example, memoization allows a function to avoid recomputing values by keeping it in a cache, and the memoized values needs to be stored into some local state.
They hide private data to the outside world
Javascript, for example, is notorious for lacking a way to explicitly hide state that is local to a function. Even with the advent of classes, the engine itself has no mechanism for private variables. Closures allow variables to be accessed only by the returned function, and the caller of that returned function has no way to access it.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK