Note: This concept is touched upon in Lambda function in list comprehensions, but not to a sufficient depth for my understanding.
We start with an example (in Python) of a closure:
def enclosing_function(free_variable):
def nested_function(x):
return x + free_variable
return nested_function
closure = enclosing_function(1) # closure is the "addition by 1" function
print(closure(37) == 38) # prints: True
A closure is created by evaluating an enclosing function. The enclosing function contains a nested function, the return value of which contains the (evaluated) free variable. Mathematically, this is equivalent to evaluating a non-empty proper subset of the input variables of a function of more than one variable. In particular, the above example is equivalent to setting y = 1 in the function f(x, y) = x + y.
An alternative implementation is to have the enclosing function return an anonymous function. In this case, the return value is called an anonymous closure:
def enclosing_function(free_variable):
return lambda x: x + free_variable
We also have anonymous enclosing functions:
print((lambda free_variable: (lambda x: x + free_variable))(1)(37)) # prints: 38
Now, let's set NUMBER_OF_LAMBDAS = 3
and look at two ways of constructing a list of
anonymous functions:
a = [lambda x: y for y in range(NUMBER_OF_LAMBDAS)]
b = [lambda x: 0, lambda x: 1, lambda x: 2]
One would naively assume that the lists are the same, but in fact this is not true:
for i in range(NUMBER_OF_LAMBDAS):
z = a[i]('foo')
print(f'a_{i}(\'foo\') = {z}')
# Prints:
# a_0('foo') = 2
# a_1('foo') = 2
# a_2('foo') = 2
for i in range(NUMBER_OF_LAMBDAS):
z = b[i]('bar')
print(f'b_{i}(\'bar\') = {z}')
# Prints:
# b_0('bar') = 0
# b_1('bar') = 1
# b_2('bar') = 2
The aforementioned StackOverflow article shows that to obtain the expected behavior, one must explicitly define and evaluate an anonymous enclosing function in the list comprehension:
c = [(lambda w: (lambda x: w))(y) for y in range(NUMBER_OF_LAMBDAS)]
for i in range(NUMBER_OF_LAMBDAS):
z = c[i]('foobar')
print(f'c_{i}(\'foobar\') = {z}')
# Prints:
# c_0('foobar') = 0
# c_1('foobar') = 1
# c_2('foobar') = 2
a
is a list of anonymous closures. c
is a list of evalutions of anonymous enclosing functions. Why are these two lists different?