Lambda and Closure

Though Lambdas have been around for a long time, lately it has become a topic of discussion as it has been introduced in many programming languages. In the context of Lambdas, the term Closure is often mentioned which is sometimes used interchangeably, though they are not the same. This post is about the distinction between a Lambda and Closure.

Lambda functions, Lambda expressions or just Lambdas refer to the same concept. These are quite common in the context of callbacks.

First some background…

Ordinary function

This is a regular function that you encounter everyday. It has access to only the formal parameters and possibly global variables. All the variables in the function are bound. In other words, there are no free or undefined variables.

Typically these are named functions which can be referenced or invoked using the function name.

Note: Functions do not maintain state information

Function object

If you need a function that should maintain some state information, then function object is the answer. It is just an Object (instance of a Class) with state information, and defines a member function that can be invoked. This requires a class defined and an object instantiated explicitly.

Anonymous function

Sometimes, there is a need for simple functions which will be used in a very local context. If the function is used only once, or a limited number of times, an anonymous function may be syntactically lighter than using a named function.

And now the specifics…

Lambda

The term Lambda sounds more mathematical than computer programming. And it is, because it originated from Lambda Calculus, introduced by mathematician Alonzo Church, in which all functions are anonymous.

Often, Lambdas are referred to as merely unnamed or anonymous functions. That is only one aspect of it. It’s more than an ordinary function because it can support an anonymous function object too.

A Lambda, like an ordinary function can operate on its formal parameters. However, in practice other variables external to the lambda function may be required (e.g. as state information). These are called free variables as they exist outside the scope of the Lambda, in the surrounding context, called the environment.

Depending on the variables referenced by the Lambda expression, it can be one of two forms.

Closed: If every variable referenced in the expression is bound to some data, either in the formal parameters or the environment. That is, there are no free variables and the expression can be evaluated.

Open: If there are any unbound variables in the expression that require data from the surrounding environment. That is, there are yet to be defined free variables and the expression cannot be evaluated.

Closure

The term Closure arises from the act of "closing" the function literal by "capturing" the bindings of its free variables. A lambda expression whose open bindings (free variables) have been closed by (or bound in) the lexical environment, resulting in a closed expression, or Closure.

An open expression has to be first closed, before it can be evaluated. The closure of a lambda expression is the subset of definitions in its environment that give values to the free variables contained in that lambda expression, effectively closing the expression. This turns an open lambda expression, which cannot be evaluated yet, into a closed lambda expression, which can then be evaluated, since all the symbols contained in it are now defined.

A Lambda exists only in the source code and not at runtime. The Closure, is the runtime effect of a lambda expression, which is the generation of the function value (the object)at runtime.

Note: A Lambda expression with no free variables, is a closed term. Thus, a function value created at runtime from this function literal is not a closure in the strictest sense, because it is already closed as written.

C++ Example

Think of Lambdas as a convenient way to create anonymous functions and function objects. C++ allows us to examine the nuances of Lambda expressions as you have to explicitly specify the free variables to be captured.

In the sample below, the Lambda expression has no free variables (empty capture list). It’s essentially an ordinary function and no closure is required. So it can be passed as a function pointer argument.

void register_func(void(*f)(int val))   // Works only with EMPTY capture list
{
    int val = 3;
    f(val);
}


int main()
{
    int env = 5;

    register_func( [](int val){ /* lambda body */ } );
}

As soon as a free variable is introduced, a Closure has to be generated which is no longer an ordinary function and produces an error.

no suitable conversion function from "lambda []void (int val)->void" to "void (*)(int val)" exists

The error can be fixed with a function wrapper std::function which accepts any callable target.

void register_func(std::function f)
{
    int val = 3;
    f(val);
}

int main()
{
    int env = 5;

    register_func( [env](int val){ /* lambda body */ } );
}

The distinction between a lambda and the corresponding closure is precisely equivalent to the distinction between a class and an instance of the class. A class exists only in source code; it doesn’t exist at runtime. What exists at runtime are objects of the class type. Closures are to lambdas as objects are to classes. This should not be a surprise, because each lambda expression causes a unique class to be generated (during compilation) and also causes an object of that class type, a closure to be created (at runtime).
– Scott Myers –