Liaison dynamique en Common Lisp
Cette question est une extension du cadrage Common Lisp (dynamique vs lexical)
J'ai lu et (espérons-le) compris les concepts de portée et d'étendue en Common Lisp (lien: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html), mais je suis incapable de comprendre les trois exemples suivants. Tous les exemples sont exécutés sur une nouvelle session lisp dans SBCL / Slime / Emacs.
Exemple 1: tirages 5 et 5
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(fun1 5)
Exemple 2: tirages 5 et 100
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
Exemple 3: tirages 5, 5 et 100
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
x
Je comprends pourquoi fun1 imprime toujours 5 (en raison de la portée lexicale, mais veuillez corriger si je me trompe). Ce que je ne comprends pas, c'est pourquoi fun2 imprime 5 dans l'exemple 1, 100 dans l'exemple 2 et encore 5 dans l'exemple 3?
- Exemple 1: x, une variable avec une portée indéfinie, est mis à 5 dans fun1 et en conséquence fun2 accède à cette valeur. Est-ce une interprétation correcte?
- Exemple 2: x est mis à 100 par defvar , mais pourquoi n'est-il pas remis à 5 lorsque fun1 est appelé? Je pensais que les liaisons avaient lieu lorsque les fonctions sont appelées ou est-ce quand elles sont définies? Il semble que x n'est pas encore lié lorsque fun1 est défini, et donc la liaison de x dans fun1 (qui a une portée lexicale) n'est pas vue par le reste du programme et alors la liaison "globale" a lieu avec la defvar suivante . Le comportement x dans l'appel de fonction est-il alors dû à l'observation lexicale dans fun1 mais pas d'ombrage dynamique pour fun2 ? C'est-à-dire qu'il y a deux instances différentes de x ici puisque fun1 a défini son x en premier et n'a pas vu de x "global" à l'époque.
- Exemple 3: Il apparaît ici que puisque x est défini globalement en premier, fun1 et fun2 font référence à la même instance de x et donc sa valeur est mise à jour pendant fun1 et appliquée également pendant fun2 (les deux sont 5)? De plus, j'obtiens 100 quand je demande la valeur de x à la fin (pourquoi? Quand fun2 renvoie 5?)
Cela a quelque chose à voir avec l'extrait suivant du livre Common Lisp de Guy Steel, mais je n'arrive pas à comprendre:
"Les constructions qui utilisent la portée lexicale génèrent effectivement un nouveau nom pour chaque entité établie à chaque exécution. Par conséquent, l'observation dynamique ne peut pas se produire (bien que l'observation lexicale puisse être). Ceci est particulièrement important lorsque l'étendue dynamique est impliquée.
L'énoncé suivant est-il toujours vrai (source: https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):
La règle de liaison en Lisp est la suivante: une utilisation d'un nom est liée à la déclaration la plus récente de ce nom qui est toujours active.
Je commence à comprendre certaines des parties, mais je ne peux pas obtenir une compréhension holistique des trois parties, il serait donc très utile que vous puissiez m'aider.
Réponses
Exemple 1: x, une variable avec une portée indéfinie, est mis à 5 dans fun1 et par conséquent fun2 accède à cette valeur. Est-ce une interprétation correcte?
Surtout, permettez-moi de développer cela.
Quand xest déclaré par defvar, la variable est déclarée comme spéciale, et à partir de maintenant xest toujours considérée comme une variable spéciale et liée dynamiquement. Quand vous appelez:
(fun1 5)
La liaison dans fun1est effectuée dynamiquement, ce qui signifie que la valeur de retour de fun1et fun2est basée sur la liaison dynamique actuelle de x.
Exemple 2: [...] Ie il y a deux instances différentes de x ici puisque fun1 a défini son x en premier et n'a pas vu de x "global" à l'époque.
Oui, mais ce n'est pas vrai pour tous les interprètes (voir la réponse de Sylwester). Lorsque vous définissez fun1, xn'est pas connu pour être spécial; cela signifie que la portée à ce stade du paramètre xest lexicale. Plus tard, quand defvarest évalué, la liaison de xin fun1est toujours lexicale et, en tant que telle, l'appel fun1ne modifie pas la liaison dynamique de la variable globale x.
Exemple 3: [...] De plus, j'obtiens 100 lorsque je demande la valeur de x à la fin (pourquoi? Quand fun2 renvoie 5?
Les variables spéciales ont une portée indéfinie , elles sont visibles partout, mais leurs liaisons ont une étendue dynamique , ce qui signifie qu'une liaison ne dure que tant que la forme qui l'établit.
Ici, lorsque vous demandez xau niveau supérieur, vous avez la valeur qui est globalement liée à x, 100; la valeur de 5 n'est liée que temporairement xpendant que l'appel à fun1est en vigueur.
Si vous aviez l'habitude SETFde muter une liaison, vous pouviez muter la liaison globale, mais ce n'est pas ce qui se produit pendant l'application de fonction ou les letliaisons.
Quelques annotations à votre code:
Exemple 1
(defvar x 100) ; declares X to be special, globally and locally
; also sets X to 100
(defun fun1 (x) ; X is a dynamically bound variable
(print x) ; lookup of dynamic binding of X
(fun2))
(defun fun2 ()
(print x)) ; lookup of dynamic binding of X
(fun1 5)
Exemple 2
(defun fun1 (x) ; X is a lexical local variable
(print x) ; lexical reference to X
(fun2))
(defun fun2 ()
(print x)) ; X is undeclared/undefined
; the exact behaviour is undefined in Common Lisp
; many implementations assume dynamic lookup of X
; most compilers will show a warning
; CMUCL also by default declared X globally to be special
; -> don't use this in your code
(defvar x 100) ; declares X to be special, globally and locally
; also sets X to 100
(fun1 5)
Exemple 3
(defvar x 100) ; declares X to be special, globally and locally
; also sets X to 100
(defun fun1 (x) ; X is a dynamically bound variable
(print x) ; lookup of dynamic binding of X
(fun2))
(defun fun2 ()
(print x)) ; lookup of dynamic binding of X
(defvar x 100) ; does nothing
; -> X is already declared special
; -> X already has a value
; see also: DEFPARAMETER
(fun1 5)
x ; lookup of global (or thread local) value of X
Votre astuce fonctionne différemment dans différentes implémentations. Par exemple. dans CLISP, qui ne compile pas leurs fonctions à la volée, se comportera exactement de la même manière dans les deux premiers exemples et exactement le même que votre sortie si vous compilez les fonctions au fur et à mesure.
La portée dynamique signifie que la portée lexicale ne s'applique pas:
(defparameter *test* 100)
(defun print-test ()
(print *test*))
(defun call-print-test-with (*test*)
(print-test))
(print-test) ; prints 100
(call-print-test 10) ; prints 10
Parce que *test*le changement dynamique (global) d'une variable locale avec le même nom l'écrase temporairement jusqu'à ce que l'étendue qui l'écrase disparaisse. C'est ce que signifie dynamique.
Si la *test*portée était lexicale, les deux seraient imprimés 100.
C'est pourquoi vous devez toujours utiliser *earmuffs*sur les globaux. Si vous avez défini une variable quelque part avec defvarou en defparameterutilisant la même chose comme paramètre ou variable locale quelque part, vous pouvez changer la variable temporairement sans le savoir et il peut être très difficile de trouver où cela se produit! Lorsque les gens voient *earmuffs*dans les paramètres et letqu'ils comprennent que c'est l'intention. par exemple.
(with-output-to-string (*standard-output*)
(some-function-whose-printed-output-you-want))
; ==> a string with the actual output
La fonction qui est appelée n'est pas la plus sage. Il pense qu'il imprime sur stdout mais vous l'avez encapsulé et modifié le flux de sortie pendant son exécution.