1

Coding Interview — Closures in Details

 1 year ago
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.
neoserver,ios ssh client

Coding Interview — Closures in Details

1*KL19CtrG03XqQ-bUvNXh0g.jpeg

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
1*3An4Fhfc2RJkOCpHF9xFtw.png

Simple C code

In this sample C program, there’s 2 scopes in action :

  • The global scope where myGlobal , myOtherGlobal and work exist until the program exit
  • The work function scope, where a , b and myLocal 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.

1*flaAptkDVgifWnsONeyrQQ.png

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 into main scope
  • The work function is invoked and its scope is created
  • myLocal variable is created within work scope
  • work return and its scope is destroyed
  • main 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.

1*FmjACNZ7UixZMRS_-r6yQQ.png

In this JavaScript snippet, it’s pretty straightforward :

  • The global scope contains a and b and they are accessible from anywhere
  • a contains local
  • b contains otherLocal

Hence, inside of a scope, I have access to :

  • local once declared
  • b since it’s the global scope
  • a itself for the same reason and to allow recursion
1*mN-CHVByt9BLJ1Bx2mmSBQ.png

Now this example is a little bit more complex. All the previous statements remain true, with additional informations :

  • Inside a , we have access to a , a2 , local and also b
  • Inside b , we don’t have access to a2 because it exists outside its scope
  • a2 shares the same scope as a 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.

1*-oYKBebjgdm3HpvZ06uoug.png

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 ?

1*9bwWfrz_cCKuD6WmTscPvA.png

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.

1*qLxSmNh77lgAaH4PHQpPqw.png

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 within a and outlives a because it’s referenced in a3
  • value2 is created within a2 and outlives a2 because it’s referenced in a3

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK