4 langage de comparaison de fermeture (Python, JavaScript, Java, C ++)

Aperçu

La fermeture est comme JavaScript, mais j'ai eu l'occasion d'essayer d'utiliser la fermeture en Python, je vais donc la comparer dans plusieurs langages.

Si vous faites une erreur, faites-le nous savoir dans les commentaires! Bien sûr, je vais essayer de ne faire aucune erreur.

Pour JavaScript

function outer(){
    let a = 0;
    let inner = () => {
      console.log(a++);
    };
    return inner;
}

let fn = outer();

>>> fn();
0
>>> fn();
1
>>> fn();
2

La variable a reste non récupérée par le GC car la variable interne contient une référence à a. Simple et facile à comprendre. Cependant, notez que a est conservé même si a est passé comme argument de external comme indiqué ci-dessous.

function outer(a){
    let inner = () => {
      console.log(a++);
    };
    return inner;
}

let fn = outer(0);

>>> fn();
0
>>> fn();
1
>>> fn();
2

Essayez d'exécuter la fermeture dans un emplacement différent de celui lors de sa définition

En conclusion, bien sûr, vous pouvez vous référer aux variables au moment de la définition même dans des endroits différents de ceux au moment de la définition.

Créez un fichier .mjs pour exécuter l'instruction d'importation sur le nœud et utilisez la commande node --experimental-modules.

module.mjs


// export let a = 1;
//ne pas exporter un
let a = 1;

export function outer(){
  let b = 1000;
  let inner1 = ()=>{
    console.log(b++);
  }
  return inner1;
}

//Pas une fonction en fonction
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)

production:


$ 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

De cette façon, a n'est pas défini, mais il peut être référencé dans inner2. Cela signifie qu'en JavaScript, une fonction devient une fermeture même si ce n'est pas une fonction en fonction.

En fait, lancez-le avec Firefox (66.0.3).

$ cp closure.mjs closure.js
$ cp module.mjs module.js

Réécrivez l'instruction d'importation de fermeture.js en tant que import * sous la forme m depuis "./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>

Accédez à fermeture.html avec Firefox et vérifiez le journal.

1000
1001
1002
1
2
3
ReferenceError: a is not defined[Détails]

Le résultat était exactement le même. En Python, la portée des variables globales est limitée à ce fichier, mais si vous utilisez la fonction d'importation / exportation et n'exportez pas cette variable, est-ce la même chose en JavaScript? (C'est naturel car il n'exporte pas)

Pour Python

def outer():
    a = 0
    def inner():
        nonlocal a
        print(a)
        a += 1
    
    return inner

fn = outer()

>>> fn()
0
>>> fn()
1
>>> fn()
2

Python est presque le même et facile à comprendre, mais il y a un supplément. Son nom n'est pas local. Obligatoire lorsque vous essayez de modifier une variable dans la portée externe. À propos, si global a est utilisé au lieu de non local, alors a fera référence à la variable globale a.

L'instruction> nonlocal garantit que les identificateurs répertoriés font référence à la variable précédemment liée dans la portée externe, à l'exclusion de global.

L'instruction globale signifie spécifier que les identificateurs listés doivent être interprétés comme des variables globales.

(Source: Référence du langage Python)

a = 111
def outer():
    a = 0
    def inner1():
        #Si vous voulez simplement vous y référer, vous n'avez pas besoin d'utiliser un non local
        print(a)
        #non local a ou a+=J'obtiens une erreur avec 1
        # a += 1
    def inner2():
        #global a signifie un=Voir la définition de 111
        global a
        print(a)
    
    return (inner1, inner2)

inner1, inner2 = outer()

>>> inner1()
0
>>> inner2()
111

#Evidemment, mais de la fermeture
#A à l'intérieur de la fonction externe référencée=0 est inaccessible de l'extérieur
#Dans ce cas a, qui est une variable globale=111 est imprimé
>>> print(a) 
111

Essayez de l'exécuter dans un emplacement différent de celui où il a été défini

module.py


a = 1

def outer():
    b = 1000
    def inner1():
        nonlocal b
        print(b)
        b += 1
    return inner1

#inner2 n'est pas une fonction en fonction
def inner2():
    #J'obtiens une erreur si je ne fais pas de global a
    global a
    print(a)
    a += 1

closure.py


from module import *

inner1 = outer()

inner1()
inner1()
inner1()

inner2()
inner2()
inner2()

production:

$ python closure.py
1000
1001
1002
1
2
3

C'est comme JavaScript!

(J'ai écrit qu'il est différent de JavaScript car j'obtiens une erreur sans dire un global dans inner2, mais je l'ai corrigé en le faisant remarquer dans le commentaire.)

Pour Java

Il semble que Java (7 ou antérieur) n'ait pas de fermeture. Vous pouvez faire quelque chose de similaire (mais avec des restrictions) en utilisant une classe anonyme dans votre fonction. L'expression Lambda a été introduite à partir de Java8, mais il semble que ce ne soit pas non plus une fermeture. Les liens suivants, y compris leur historique, sont détaillés et très faciles à lire et recommandés.

Je n'ai pas encore lu la partie 2, mais je la posterai.

Ce qui suit est une explication approximative. Tout d'abord, essayons un exemple d'utilisation d'une classe anonyme. En Java, les fonctions ne sont pas des objets de première classe, alors que se passe-t-il si nous renvoyons un objet de classe anonyme qui n'a qu'une seule fonction en tant que membre de la fonction externe à la place?

interface Inner {
    public void print();
}

public class ClosureTest {

    public Inner outer() {
        //Erreur si final n'est pas utilisé ici
        final int a = 0;
        
        return new Inner() {
            public void print() {
                System.out.println(a);
                
                //Parce que c'est définitif, un++Ne peux pas
            }
        }
    }
    
    public static void main(String[] args) {
		ClosureTest ct = new ClosureTest();
		
		Inner inner = ct.outer();

		inner.print(); 

	}

}

Comme dans l'exemple ci-dessus, vous devez ajouter final, vous ne pouvez donc pas le faire comme JavaScript ou Python. Cependant, puisque final est juste final pour référence, il est possible de changer la valeur de l'élément pour chaque exécution en changeant la variable a en un tableau ou ArrayList. En d'autres termes, la même chose peut être réalisée.

Vient ensuite l'expression lambda. Dans le cas d'une expression lambda, lors du référencement d'une variable hors de portée, cette variable n'a pas besoin d'être finale. Cependant, j'obtiens une erreur lorsque je change la valeur.

public class Closure {
	public static void main(String... args) {
            //Il n'a pas besoin d'être définitif contrairement à la classe anonyme!
            int a = 0;
            //Mais un++J'ai une erreur dans la pièce
            Runnable r = () -> System.out.println(a++);
	    r.run();
	  } 
}

Les détails de l'erreur sont les suivants.

Exception in thread "main" java.lang.Error: Unresolved compilation problem: Local variable a defined in an enclosing scope must be final or effectively final

Cela signifie que la variable a doit être définitive ou pratiquement définitive. Substantiellement définitif signifie ne pas faire de changements comme l'exemple ci-dessus.

En d'autres termes, la gestion lors du référencement de variables hors de portée est (presque) la même pour les classes anonymes et les expressions lambda.

Avant, quand j'ai découvert les classes anonymes, les interfaces fonctionnelles, la notation lambda, etc., je me demandais ce que c'était, mais du point de vue de la fermeture, je sens que je peux le comprendre.

Pour C ++

Il semble facile d'écrire à l'aide d'expressions lambda C ++ 11.

Tout d'abord, c'est l'expression lambda.

[](inta,intb) -> int { return a + b; }

Utilisez comme suit.

auto fn = [](inta,intb) -> int { return a + b; }
int c = fn();

La partie "-> int" indique le type de retour de cette fonction. Il peut être omis comme suit.

[](inta,intb) { return a + b; }

[] Sera décrit plus tard.

Et

Cette expression lambda définit un objet fonction à la volée:

struct F {
 auto operator()(inta,intb) const -> decltype(a + b)
 {
    return a + b;
 }
};

(Source: cpprefjp - Référence japonaise C ++)

Il est intéressant de surcharger () pour réaliser un objet fonction. La raison pour laquelle il y a deux valeurs de retour, auto et decltype (a + b), est voir ici.

[] Signifie capture.

Les expressions Lambda ont une fonction appelée «capture» qui permet aux variables automatiques en dehors de l'expression lambda d'être référencées dans l'expression lambda. La capture est spécifiée dans le bloc [] au début de l'expression lambda, appelée le lambda-introducer.

La capture comprend la capture de copie et la capture de référence, et vous pouvez spécifier la méthode à capturer par défaut et la méthode à capturer les variables individuelles.

(Source: cpprefjp - Référence japonaise C ++)

Voici un exemple de capture

#include <iostream>

using namespace std;

int main(){
  int a = 1;
  int b = 2;
  
  //Copier la capture a
  auto fn1 = [a]() { cout << a << endl; };
  
  //Capture de référence a
  auto fn2 = [&a]() { cout << a << endl; };
  
  //Copier la capture de a et b
  auto fn3 = [=]() { cout << a + b << endl; };
  
  //Capture de référence de a et b
  auto fn4 = [&]() { cout << a + b << endl; };
  
  a = 1000;
  b = 2000;
  
  fn1();
  fn2();
  fn3();
  fn4();
}

production:

1
1000
3
3000

Au moment de la capture de copie, est-ce que ce sera comme suit? S'il vous plaît laissez-moi savoir si vous avez des détails.

C'est du code imaginaire
struct F {
  //N'est-ce pas le nom de la variable a et b?
  int a = 1;
  int b = 2;
  auto operator()() const -> decltype(a + b)
  {
     cout << a + b << endl;
  }
};

Et la fermeture peut être écrite comme ça.

#include <iostream>
#include <functional>

std::function<int()> outer()
{
    int a = 0;
    //Copier la 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;
}

Concernant mutable,

La variable capturée est considérée comme une variable membre de l'objet de fermeture, et l'opérateur d'appel de fonction de l'objet de fermeture est qualifié par défaut. Par conséquent, la variable capturée par copie ne peut pas être réécrite dans l'expression lambda.

Si vous souhaitez réécrire la variable capturée par copie, écrivez mutable après la liste des paramètres d'expression lambda.

(Source: cpprefjp - Référence japonaise C ++) Et cela.

Dans cet exemple, copiez un dans la portée externe et enregistrez-le en tant que variable membre dans l'expression lambda (objet fonction). La variable a copiée comme Java est const (final), mais elle peut être modifiée par la phrase mutable.

Bien sûr, dans l'exemple ci-dessus

auto inner = [&a]() mutable -> int {}

Si vous effectuez une capture de référence comme celle-ci, la destination de référence sera libérée à la fin de l'exécution external (), il doit donc s'agir d'une capture de copie.

prime

Vous pouvez utiliser des fermetures intéressantes en Python. Il existe une bibliothèque appelée http.server et vous pouvez configurer un serveur Web simple. Il est utilisé comme suit, mais le hd du deuxième argument de HTTPServer () doit être un objet de classe. Mais hd fonctionne bien avec les fermetures.

server = HTTPServer(('', int(port)), hd)
server.serve_forever()

Si hd est une fermeture:

def handler_wrapper():
    counter = [0]
    def handler(*args):
        counter[0] += 1
        return HandleServer(counter, *args)

    return handler

hd = handler_wrapper()

Si j'ai le temps, y compris pourquoi je devrais faire cela, j'aimerais l'écrire dans un article séparé.

Résumé

La fermeture est difficile.

Recommended Posts

4 langage de comparaison de fermeture (Python, JavaScript, Java, C ++)
Comparaison de vitesse de Python, Java, C ++
Comparaison grammaticale de base en cinq langues (C #, Java, Python, Ruby, Kotlin)
Comparaison de la grammaire de base entre Java et Python
Introduction à Protobuf-c (langage C ⇔ Python)
Appeler le langage C depuis Python (python.h)
Comparaison des performances de désérialisation de msgpack (C ++ / Python / Ruby)
Rendement Python express en JavaScript ou Java
Apprendre Python! Comparaison avec Java (fonction de base)
Communication socket par langage C et Python
Générer un langage C à partir d'une expression S avec Python
[C, C ++, Python, JavaScript] L Chika avec Edison
Ecrire une liste de liens en langage C dans un style orienté objet (avec comparaison de code entre Python et Java)
Premier Python 3 ~ Première comparaison ~
62 scripts de conversion décimale <-> par langage (R, Python, Java)
Exemple de fermeture Python
Python> fonction> Fermeture
Écriture de journaux dans un fichier CSV (Python, langage C)
notes de python C ++
python, openFrameworks (c ++)
paiza POH ec-campagne (C # / Java / Python / Ruby) # paizahack_01
Essayez de créer un module Python en langage C
Benchmarks langage C, Java, Python avec factorisation prime
AtCoder Beginner Contest 176 Explication de l '«étape» du problème C (Python3, C ++, Java)
L'histoire de la conversion automatique du langage de TypeScript / JavaScript / Python
Écrivons respectivement Python, Ruby, PHP, Java, JavaScript
Comparaison de CoffeeScript avec la grammaire JavaScript, Python et Ruby
Comparaison du temps d'exécution de Python SDP
Python: traitement du langage naturel
Pointeur de modèle d'extension Python C / C ++
File d'attente ALDS1_3_B langage C
Introduction au langage Python
Next Python en langage C
Comparaison du gestionnaire de packages Python
[Algorithme de langage C] Endianness
API C en Python 3
ABC147 C --HonestOrUnkind2 [Python]
Appeler des fonctions du langage C depuis Python pour échanger des tableaux multidimensionnels
AtCoder Beginner Contest 174 B Explication du problème "Distance" (C ++, Python, Java)
Mesurons le résultat de l'exécution du programme avec C ++, Java, Python.
AtCoder Beginner Contest 167 Explication d'un problème "enregistrement" (Python3, C ++, Java)
AtCoder ABC151 Problème D Comparaison de la vitesse en C ++ / Python / PyPy
Supprimez les espaces de début et de fin en Python, JavaScript ou Java
2020 Arduino / Raspberry Pi / Python / Microcomputer C apprentissage du langage ~ Livre recommandé ~
AtCoder Beginner Contest 169 B Problème Explication "Multiplication 2" (Python3, C ++, Java)
Utilisez un langage de script pour une vie C ++ confortable-OpenCV-Port Python vers C ++ -
Comportement des opérateurs de division entre entiers (langage C, C ++, Scala, Java, Rust, langage Go, PHP, JavaScript, Perl, Python, Ruby)