Closures are like JavaScript, but I had the opportunity to try closures in Python, so I'll compare them in different languages.
If you make a mistake, please let us know in the comments! Of course, I will try to make no mistakes.
function outer(){
let a = 0;
let inner = () => {
console.log(a++);
};
return inner;
}
let fn = outer();
>>> fn();
0
>>> fn();
1
>>> fn();
2
The variable a remains unrecovered by the GC because the inner holds a reference to a. Simple and easy to understand. However, note that a is retained even if a is passed as an argument of outer as shown below.
function outer(a){
let inner = () => {
console.log(a++);
};
return inner;
}
let fn = outer(0);
>>> fn();
0
>>> fn();
1
>>> fn();
2
From the conclusion, of course, you can refer to the variable at the time of definition even in a place different from the time of definition.
Create a .mjs file to execute the import statement on node and use the node --experimental-modules command.
module.mjs
// export let a = 1;
//do not export a
let a = 1;
export function outer(){
let b = 1000;
let inner1 = ()=>{
console.log(b++);
}
return inner1;
}
//Not an in-function function
export function inner2(){
console.log(a++)
}
closure.mjs
import * as m from "./module.mjs";
let fn = m.outer();
fn();
fn();
fn();
m.inner2();
m.inner2();
m.inner2();
console.log(a)
output:
$ node --experimental-modules closure.mjs
(node:12980) ExperimentalWarning: The ESM module loader is experimental.
1000
1001
1002
1
2
3
file:///***********/closure.mjs:11
console.log(a)
^
ReferenceError: a is not defined
As you can see, a is not defined, but you can refer to it in inner2. This means that in JavaScript, a function becomes a closure, even if it is not an in-function function.
Actually run it with firefox (66.0.3).
$ cp closure.mjs closure.js
$ cp module.mjs module.js
Rewrite the import statement of closure.js as import * as m from "./module.js" ;.
closure.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="module" src="closure.js"></script>
<title>test</title>
</head>
<body>
</body>
</html>
Access closure.html with firefox and check the log.
1000
1001
1002
1
2
3
ReferenceError: a is not defined[Details]
The result was exactly the same. In Python, the scope of global variables is limited to that file, but if you use the import / export function and do not export the variable, is it the same in JavaScript? (It's natural because it doesn't export)
def outer():
a = 0
def inner():
nonlocal a
print(a)
a += 1
return inner
fn = outer()
>>> fn()
0
>>> fn()
1
>>> fn()
2
Python is pretty much the same and easy to understand, but with one extra. Its name is nonlocal. Required when trying to change a variable in the outer scope. By the way, if global a is used instead of nonlocal, then a will refer to the global variable a.
The> nonlocal statement makes sure that the listed identifiers refer to the previously bound variable in the scope one outside except global.
The global statement means to specify that the listed identifiers should be interpreted as global variables.
(Source: Python Language Reference)
a = 111
def outer():
a = 0
def inner1():
#If you just want to refer to it, you don't have to use nonlocal a
print(a)
#nonlocal a or a+=I get an error with 1
# a += 1
def inner2():
#global a means a=See the definition of 111
global a
print(a)
return (inner1, inner2)
inner1, inner2 = outer()
>>> inner1()
0
>>> inner2()
111
#Obviously, but from the closure
#A inside the referenced outer function=0 is inaccessible from the outside,
#In this case a, which is a global variable=111 is printed
>>> print(a)
111
module.py
a = 1
def outer():
b = 1000
def inner1():
nonlocal b
print(b)
b += 1
return inner1
#inner2 is not an in-function function
def inner2():
#I get an error if I don't do global a
global a
print(a)
a += 1
closure.py
from module import *
inner1 = outer()
inner1()
inner1()
inner1()
inner2()
inner2()
inner2()
output:
$ python closure.py
1000
1001
1002
1
2
3
It's just like JavaScript!
(I wrote that it is different from JavaScript because I get an error without saying global a in inner2, but I corrected it by pointing out in the comment.)
It seems that Java (7 or earlier) does not have closures. You can do something similar (but with restrictions) by using an anonymous class in your function. Lambda expressions were introduced from Java8, but it seems that they are not closures either. The following links, including their background, are detailed and very easy to read and recommended. -Java 8: Lambda expression, part 1 -Java 8: Lambda expression, part 2
I haven't read Part 2 yet, but I'll post it.
The following is a rough explanation. First, let's try an example of using an anonymous class. In Java, a function is not a first-class citizen, so what if we return an anonymous class object that has only one function as a member from the outer function instead?
interface Inner {
public void print();
}
public class ClosureTest {
public Inner outer() {
//Error if final is not used here
final int a = 0;
return new Inner() {
public void print() {
System.out.println(a);
//Because it's final, a++Can not
}
}
}
public static void main(String[] args) {
ClosureTest ct = new ClosureTest();
Inner inner = ct.outer();
inner.print();
}
}
As in the example above, you have to add final, so you can't do it like JavaScript or Python. However, since final is just final for reference, it is possible to change the value of the element for each execution by making the variable a an array or ArrayList. In other words, the same thing can be achieved.
Next is a lambda expression. In the case of a lambda expression, when referencing an out-of-scope variable, that variable does not have to be final. However, I get an error when I change the value.
public class Closure {
public static void main(String... args) {
//It doesn't have to be final unlike the anonymous class!
int a = 0;
//But a++I get an error in the part
Runnable r = () -> System.out.println(a++);
r.run();
}
}
The details of the error are as follows.
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Local variable a defined in an enclosing scope must be final or effectively final
This means that the variable a must be final or practically final. Substantially final means not making changes like the example above.
In other words, the handling when referencing an out-of-scope variable is (almost) the same for anonymous classes and lambda expressions.
Before, when I first learned about anonymous classes, functional interfaces, and lambda notation, I was wondering what this was, but from the perspective of closures, I think I can understand it.
It seems easy to write using a C ++ 11 lambda expression.
First, this is the lambda expression.
[](inta,intb) -> int { return a + b; }
Use as follows.
auto fn = [](inta,intb) -> int { return a + b; }
int c = fn();
The "-> int" part indicates the return type of this function. It may be omitted as follows.
[](inta,intb) { return a + b; }
[] Will be described later.
And
This lambda expression defines a function object on the fly:
struct F { auto operator()(inta,intb) const -> decltype(a + b) { return a + b; } };
(Source: cpprefjp --C ++ Japanese Reference)
It is interesting to overload () to realize a function object. The reason why the return value is auto and decltype (a + b) is see here.
[] Means capture.
Lambda expressions have a feature called "capture" that allows you to refer to automatic variables outside the lambda expression within the lambda expression. The capture is specified in the [] block at the beginning of the lambda expression, called the lambda-introducer.
Capture includes copy capture and reference capture, and you can specify which method to capture by default and which method to capture individual variables.
(Source: cpprefjp --C ++ Japanese Reference)
Below is an example of capture
#include <iostream>
using namespace std;
int main(){
int a = 1;
int b = 2;
//Copy capture a
auto fn1 = [a]() { cout << a << endl; };
//Reference capture a
auto fn2 = [&a]() { cout << a << endl; };
//Copy capture of a and b
auto fn3 = [=]() { cout << a + b << endl; };
//Reference capture of a and b
auto fn4 = [&]() { cout << a + b << endl; };
a = 1000;
b = 2000;
fn1();
fn2();
fn3();
fn4();
}
output:
1
1000
3
3000
At the time of copy capture, will it be as follows? Please let me know if you have any details.
This is imaginary code
struct F {
//Variable names are not likely to be a and b?
int a = 1;
int b = 2;
auto operator()() const -> decltype(a + b)
{
cout << a + b << endl;
}
};
And closures can be written like this.
#include <iostream>
#include <functional>
std::function<int()> outer()
{
int a = 0;
//Copy capture a
auto inner = [a]() mutable -> int {
return a++;
};
return inner;
}
int main()
{
auto inner = outer()
std::cout << inner() << std::endl;
std::cout << inner() << std::endl;
std::cout << inner() << std::endl;
return 0;
}
Regarding mutable,
The captured variable is considered a member variable of the closure object, and the closure object's function call operator is const-qualified by default. Therefore, the copy-captured variable cannot be rewritten in the lambda expression.
If you want to rewrite the copy-captured variable, write mutable after the parameter list of the lambda expression.
(Source: cpprefjp --C ++ Japanese Reference) And that.
In this example, copy a in the external scope and save it as a member variable in a lambda expression (function object). The variable a copied like Java is const (final), but it can be changed by the word mutable.
Of course, in the above example
auto inner = [&a]() mutable -> int {}
If you do a reference capture like this, the reference destination will be released at the end of outer () execution, so it must be a copy capture.
You can use interesting closures in Python. There is a library called http.server, and you can set up a simple web server. It is used as follows, but the hd of the second argument of HTTPServer () must be a class object. But hd works well with closures.
server = HTTPServer(('', int(port)), hd)
server.serve_forever()
If hd is a closure:
def handler_wrapper():
counter = [0]
def handler(*args):
counter[0] += 1
return HandleServer(counter, *args)
return handler
hd = handler_wrapper()
If I have time, including why I should do this, I would like to write it as a separate article.
Closures are difficult.
Recommended Posts