48 votes

Variables dynamiques et lexicales dans Common Lisp

Je suis en train de lire le livre "Pratique Common Lisp" par Peter Seibel.

Dans le Chapitre 6, "Variables" sections "Lexicale des Variables et des Fermetures" et "Dynamique, une.k.un. Spécial, Les Variables". http://www.gigamonkeys.com/book/variables.html

Mon problème est que les exemples dans les deux articles montrent comment (passons ...) peuvent ombre des variables globales et ne veut pas vraiment dire la différence entre la Dynamique et Lexicales de vars.

Je comprends comment les fermetures de travail, mais je n'ai pas vraiment d'obtenir ce qui est si spécial au sujet de la laisser dans cet exemple:

(defvar *x* 10)

(defun foo ()
  (format t "Before assignment~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "After assignment~18tX: ~d~%" *x*))


(defun bar ()
  (foo)
  (let ((*x* 20)) (foo))
  (foo))


CL-USER> (foo)
Before assignment X: 10
After assignment  X: 11
NIL


CL-USER> (bar)
Before assignment X: 11
After assignment  X: 12
Before assignment X: 20
After assignment  X: 21
Before assignment X: 12
After assignment  X: 13
NIL

Je me sens comme il n'y a rien de spécial qui se passe ici. L'extérieur foo dans la barre de incrémente le global x, et foo entouré de laisser dans la barre de incréments de l'ombre x. Quel est le big deal? Je ne vois pas comment est-ce censé expliquer la différence entre le lexique et dynamique de vars. Cependant, le livre continue comme ça:

Alors, comment cela fonctionne? Comment ne LAISSEZ sais que quand il se lie xc'est censé créer une liaison dynamique plutôt normal qu'un lexicale de liaison? Il le sait parce que le nom a été déclaré spécial.12 Le nom de chaque variable définie avec DEFVAR et DEFPARAMETER est automatiquement déclarée à l'échelle mondiale spécial.

Ce qui devrait arriver si laisser lier x à l'aide de "normal lexicale de liaison"? Dans l'ensemble, quelles sont les différences entre dynamique et lexicales de liaison et comment est-ce l'exemple de spécial au sujet de liaison dynamique?

60voto

Rainer Joswig Points 62532

Vous dites: se sentir comme il n'y a rien de spécial qui se passe ici. L'extérieur foo dans la barre de incrémente le global x, et foo entouré de laisser dans la barre de incréments de l'ombre x. Quel est le big deal?

Le "spécial" qui se passe ici, c'est que LAISSER pouvez l'ombre de la valeur de *x*. Avec lexicale des variables qui n'est pas possible.

Le code déclare *x* pour être spécial via le DEFVAR.

Dans FOO maintenant la valeur de *x* est regardé dynamique. TOTO va prendre le courant de liaison dynamique de *x* ou, à défaut, à la valeur du symbole du symbole *x*. Une nouvelle dynamique de la liaison peut, par exemple, être introduit avec LAISSEZ.

Une variable lexicale sur l'autre main doit être présent dans l'environnement lexical quelque part. LAISSEZ LAMBDA, DEFUN et d'autres peuvent introduire de telles variables lexicales:

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

Si notre code:

(defvar x 0)

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

Alors X étaient spéciaux dans tous les cas, en raison de la DEFVAR déclaration, qui déclare X de l'être. De ce fait, il y a la convention de déclarer des variables spéciales comme *X*. Ainsi, seules les variables avec des étoiles autour d'eux sont spéciales, par convention. C'est une convention utile.

Dans votre code, vous devez alors:

(defun bar ()
  (foo)
  (let ((*x* 20))
    (foo))
  (foo))

Depuis *x* a être déclarées spéciales via le DEFVAR, de la permettent de construire introduit une nouvelle dynamique de liaison pour *x*. TOTO est alors appelée. Depuis l'intérieur de FOO l' *x* utilise de liaison dynamique, il regarde l'actuel et constate que *x* est lié dynamiquement à 20.

La valeur d'une variable spéciale est en cours de liaison dynamique.

(defun foo-s ()
  (declare (special *x*))
  (+ *x* 1))

Si la variable a été déclarée spécial par un DEFVAR ou DEFPARAMETER, puis les locaux déclaration spéciale peut être omis.

Une variable lexicale directement référence à la variable de liaison.

(defun foo-l (x)
  (+ x 1))

Nous allons voir dans la pratique:

(let ((f (let ((x 10))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (print (funcall f))))

Ici, les variables sont lexicale. Dans le formulaire 2 de la LAISSER ne fera pas de l'ombre à l'X dans notre fonction f. Il ne peut pas. La fonction utilise le lexique lié variable, introduit par le LET ((X 10) . Entourant l'appeler avec un autre lexical lié à X dans la formule 2 n'a aucun effet sur notre fonction.

Nous allons essayer de variables spéciales:

(let ((f (let ((x 10))
           (declare (special x))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (declare (special x))
    (print (funcall f))))

Que faire maintenant? Cela fonctionne?

Il ne le fait pas!

La première forme d'appels de la fonction et il essaie de rechercher la dynamique de la valeur de X et de y est aucune.

Nous obtenons une erreur dans le formulaire 1 que X est indépendant, car il n'existe pas de liaison dynamique en vigueur.

Forme 2, le travail, depuis le LAISSER avec la déclaration spéciale introduit une liaison dynamique pour X.

26voto

Kyle Cronin Points 35834

Lorsqu'une variable est lexicalement étendue, le système ressemble à l'endroit où la fonction est définie pour trouver la valeur d'une variable indépendante. Lorsqu'une variable est de façon dynamique étendue, le système ressemble à l'endroit où la fonction est appelée pour trouver la valeur de la variable indépendante. Les Variables en Common Lisp sont tous lexicale par défaut; toutefois, de façon dynamique étendue variables peuvent être définies au plus haut niveau à l'aide de defvar ou defparameter.

Un exemple plus simple

portée lexicale (avec setq):

(setq x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 3

dynamique de la portée (avec defvar):

(defvar x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 4

Comment le savoir si une variable est lexicales ou dynamique? Il n'a pas. D'autre part, lorsque toto va trouver la valeur de X, il va d'abord trouver le lexique de la valeur définie au niveau supérieur. Ensuite, il vérifie si la variable est censé être dynamique. Si c'est le cas, foo ressemble à l'appel de l'environnement, qui, dans ce cas, utilise le laisser faire de l'ombre à la valeur de X à 4.

(note: ceci est une simplification excessive, mais il vous aidera à visualiser la différence entre les différentes règles de portée)

11voto

Mark Cox Points 217

Peut-être que cet exemple aidera.

 ;; the lexical version

(let ((x 10)) 
  (defun lex-foo ()
    (format t "Before assignment~18tX: ~d~%" x)
    (setf x (+ 1 x))
    (format t "After assignment~18tX: ~d~%" x)))

(defun lex-bar ()
  (lex-foo)
  (let ((x 20)) ;; does not do anything
    (lex-foo))
  (lex-foo))

;; CL-USER> (lex-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 11
;; After assignment  X: 12
;; Before assignment X: 12
;; After assignment  X: 13

;; the dynamic version

(defvar *x* 10)
(defun dyn-foo ()
  (format t "Before assignment~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "After assignment~18tX: ~d~%" *x*))

(defun dyn-bar()
  (dyn-foo)
  (let ((*x* 20))
    (dyn-foo))
  (dyn-foo))

;; CL-USER> (dyn-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 20
;; After assignment  X: 21
;; Before assignment X: 11
;; After assignment  X: 12

;; the special version

(defun special-foo ()
  (declare (special *y*))
  (format t "Before assignment~18tX: ~d~%" *y*)
  (setf *y* (+ 1 *y*))
  (format t "After assignment~18tX: ~d~%" *y*))

(defun special-bar ()
  (let ((*y* 10))
    (declare (special *y*))
    (special-foo)
    (let ((*y* 20))
      (declare (special *y*))
      (special-foo))
    (special-foo)))

;; CL-USER> (special-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 20
;; After assignment  X: 21
;; Before assignment X: 11
;; After assignment  X: 12
 

9voto

skypher Points 2158

Vous pouvez aussi demander à votre Lisp de lier dynamiquement les variables locales:

 (let ((dyn 5))
  (declare (special dyn))
  ... ;; DYN has dynamic scope for the duration of the body
  )
 

8voto

juanitofatas Points 1651

Exemple de réécriture à partir de PCL.

;;; Common Lisp is lexically scoped by default.

λ (setq x 10)
=> 10

λ (defun foo ()
    (setf x (1+ x)))
=> FOO

λ (foo)
=> 11

λ (let ((x 20))
    (foo))
=> 12

λ (proclaim '(special x))
=> NIL

λ (let ((x 20))
    (foo))
=> 21

Pourtant, une autre grande explication Sur le langage Lisp, chapitre 2.5 Portée:

Common Lisp est un lexicalement étendue de Lisp. Le schéma est le plus ancien dialecte portée lexicale; avant Régime dynamique de la portée a été considéré comme l'un des traits caractéristiques du Lisp.

La différence entre lexique et dynamique de la portée dépend de la façon dont une mise en œuvre traite de variables libres. Un symbole est lié à une expression si elle a été établie comme une variable, soit en apparaissant comme un paramètre ou d'une variable de liaison des opérateurs tels que que et ne. Les symboles qui ne sont pas liés sont dit d'être libre. Dans cet exemple, la portée entre en jeu:

(let ((y 7)) 
  (defun scope-test (x)
  (list x y)))

Dans le defun expression, x est lié et y est gratuit. Variables libres sont intéressants parce qu'il n'est pas évident que leurs valeurs doivent être. Il n'y a pas d'incertitude sur la valeur d'une variable liée-lors de champ-test est appelée, la valeur de x doit être tout ce qui est passé comme argument. Mais quelle doit être la valeur de y? C'est la réponse à la question par le dialecte du champ d'application des règles.

Dans la dynamique d'une étendue de Lisp, pour trouver la valeur d'une variable lors de l'exécution de champ-test, nous regardons en arrière à travers la chaîne de fonctions qui l'a appelée. Lorsque nous trouvons un environnement où y était lié, que la liaison de y sera celui utilisé dans le champ d'application de test. Si nous trouvez pas, nous prenons la valeur globale de y. Ainsi, dans la dynamique d'une étendue de Lisp, y aurait de la valeur qu'il avait dans l'appel de l'expression:

> (let ((y 5)) (scope-test 3))
    (3 5)

Avec une dynamique étendue, cela signifie que rien de ce qui y était lié à 7 lors de champ-test a été défini. Tout ce qui compte c'est que y'avait une valeur de 5, lorsque le champ d'application-test a été appelé.

Dans un lexicalement étendue de Lisp, au lieu de regarder en arrière à travers la chaîne de l'appel de fonctions, nous regardons en arrière à travers les environnements contenant à la fois la fonction a été définie. Dans un lexicalement étendue de Lisp, par exemple, serait attraper la liaison de y où la portée de test a été défini. Donc, c'est ce qui serait le cas en Common Lisp:

> (let ((y 5)) (scope-test 3))
    (3 7)

Ici, la liaison de y à 5 à l'heure de l'appel n'a aucun effet sur la valeur retournée.

Mais vous pouvez toujours obtenir la dynamique portée par la déclaration d'une variable spéciale, portée lexicale est la valeur par défaut en Common Lisp. Dans l'ensemble, la communauté Lisp semble à la vue de l'adoption de la dynamique de la portée avec un peu de regret. Pour une chose, il a utilisé pour mener à horriblement insaisissable bugs. Mais la portée lexicale est plus qu'un moyen d'éviter les bugs. La prochaine section va montrer, il rend également possible que certaines nouvelles techniques de programmation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X