2017-06-22 2 views
1

Actuellement en train d'apprendre le lisp commun, suivant Practical Common Lisp de Peter Seibel (je suis au chapitre 11, sur les collections), j'ai des difficultés à comprendre comment fonctionne setf derrière le capot.Comment fonctionne setf sous le capot?

Compte tenu de cette expression:

(setf a 10) 

avoir bien compris comment l'interprète peut (1) récupérer la variable nommée a, et (2) modifier la valeur il pointe à 10.

Maintenant, dans le cas de collections particulières, par exemple des listes, des vecteurs ou des tables de hachage, setf peut également être utilisé pour modifier les valeurs contenues dans la collection. Par exemple, avec un vecteur:

(defparameter *x* '(a b c d)) 
(setf (elt *x* 1) bb) 

Cela me rend méfiant au sujet setf, car il est éventuellement trouver des informations non accessibles trivialement, ou de faire de la magie noire. Je vois plusieurs possibilités.

1. setf est une fonction

L'expression (elt *x* 1) revient 'b, donc setf travaille pratiquement avec (setf b bb). Je ne comprends pas alors comment setf peut déduire quel objet (ici, la liste *x*) il doit modifier, sans avoir la valeur de retour de elt contenant à la fois une indication qu'il provient d'une collection, et un pointeur sur ladite collection. Cela semble compliqué.

2. setf est une macro

L'idée est, comme setf est une macro, il travaille directement avec le (setf (elt *x* 1) bb) et peut donc extraire la partie elt *x* 1 à déduire quel objet/collection est utilisé, et doit donc être modifié.

Il ne semble pas très efficace, ni fiable, ni résistant aux opérations complexes. Cependant, depuis que je suis incapable d'exécuter ce code:

(funcall (find-symbol (concatenate 'string "E" "LT")) *x* 1) ; -> B 
(setf (funcall (find-symbol (concatenate 'string "E" "LT")) *x* 1) 'bb) ; -> ERROR : (SETF FUNCALL) is only defined for functions of the form #'symbol 

Cela me font penser que setf est une macro mettant en œuvre une heuristique très simple pour récupérer la fonction à appeler, et toutes les autres informations nécessaires. Cela semble compliqué.

3. setf est un cas particulier d'interprétation

Une autre façon d'aller pourrait être d'avoir setf être traitée différemment par l'interprète lui-même, face à la magie noire pour mettre en œuvre correctement le comportement attendu. Cela semble compliqué.

4. il y a quelque chose que je ne sais pas Lisp

Probablement la vraie réponse. Qu'est-ce que j'ai raté ?

Question supplémentaire: la méthode d'implémentation dépend-elle de la mise en œuvre de l'interpréteur Lisp? (ou, plus simplement, ce que définit exactement la norme Lisp commune à propos de l'implémentation de setf) J'utilise actuellement clisp mais des informations sur d'autres implémentations sont les bienvenues.

+2

Le hyperspec a des détails sur 'SETF'/endroits [5.1 Référence Généralisée] (http://www.lispworks.com/documentation/HyperSpec/Body/05_a.htm), bien qu'il soit assez technique. – jkiiski

Répondre

6

SETF est une macro qui définit une valeur à un place. Un lieu signifie un formulaire qui a un setf expansion. Il existe divers kinds of places intégré, et vous pouvez définir plus (voir par exemple DEFSETF et DEFINE-SETF-EXPANDER, function call forms as places et macro forms as places).

Vous pouvez obtenir l'extension setf pour un formulaire en utilisant GET-SETF-EXPANSION. Il renvoie cinq valeurs. Par exemple,

(get-setf-expansion '(elt *x* 1)) 
;=> (#:*X*660) 
; (*X*) 
; (#:NEW1) 
; (SB-KERNEL:%SETELT #:*X*660 1 #:NEW1) 
; (ELT #:*X*660 1) 

La cinquième valeur est une forme getter qui, lorsqu'elle est évaluée, renvoie la valeur actuelle de l'emplacement. Le quatrième est une forme de setter qui, une fois évaluée, définit une nouvelle valeur à l'endroit. Ici, vous pouvez voir que SBCL utilise SB-KERNEL:%SETELT pour définir la valeur.

La première valeur est une liste de noms de variables qui doivent être liés aux valeurs renvoyées par les formulaires dans la deuxième valeur lors de l'évaluation des formulaires setter/getter. La troisième valeur est une liste de variables de magasin, qui doivent être liées aux nouvelles valeurs à stocker par le setter.

Avec ceux-ci, nous pouvons définir un simple MY-SETF -macro.

(defmacro my-setf (place values-form &environment env) 
    (multiple-value-bind (vars vals stores setter) 
     (get-setf-expansion place env) 
    `(let* ,(mapcar #'list vars vals) 
     (multiple-value-bind ,stores ,values-form 
     ,setter)))) 

Tout ce que nous devons faire est de lier les variables, et évaluer le poseur. Notez que l'environnement doit être transmis à GET-SETF-EXPANSION. Nous ignorons la cinquième valeur (le getter), puisque nous n'en avons pas besoin. MULTIPLE-VALUE-BIND est utilisé pour lier les variables de magasin, car il peut y en avoir plus d'une.

(let ((list (list 1 2 3 4))) 
    (my-setf (elt list 2) 100) 
    list) 
;=> (1 2 100 4) 

(let ((a 10) (b 20) (c 30)) 
    (my-setf (values a b c) (values 100 200 300)) 
    (list a b c)) 
;=> (100 200 300) 

Il existe plusieurs façons de définir vos propres emplacements. Les moyens les plus simples sont d'utiliser DEFSETF ou de définir simplement une fonction setf avec DEFUN. Par exemple:

(defun eleventh (list) 
    (nth 10 list)) 

(defun set-eleventh (list new-val) 
    (setf (nth 10 list) new-val)) 

(defsetf eleventh set-eleventh) 

(let ((l (list 1 2 3 4 5 6 7 8 9 10 11 12 13))) 
    (setf (eleventh l) :foo) 
    l) 
;=> (1 2 3 4 5 6 7 8 9 10 :FOO 12 13) 

(get-setf-expansion '(eleventh l)) 
;=> (#:L662) 
; (L) 
; (#:NEW1) 
; (SET-ELEVENTH #:L662 #:NEW1) 
; (ELEVENTH #:L662) 


(defun twelfth (list) 
    (nth 11 list)) 

(defun (setf twelfth) (new-val list) 
    (setf (nth 11 list) new-val)) 

(let ((l (list 1 2 3 4 5 6 7 8 9 10 11 12 13))) 
    (setf (twelfth l) :foo) 
    l) 
;=> (1 2 3 4 5 6 7 8 9 10 11 :FOO 13) 

(get-setf-expansion '(twelfth l)) 
;=> (#:L661) 
; (L) 
; (#:NEW1) 
; (FUNCALL #'(SETF TWELFTH) #:NEW1 #:L661) 
; (TWELFTH #:L661) 
5

setf est une macro.

Vous pouvez lire Generalized Reference dans tous les détails sordides, mais, au fond, cela fonctionne comme ceci:

(setf symbol expression) est le même que setq

Sinon, nous avons (setq (symbol arguments) expression).

S'il y a une fonction nommée (setf symbol) (oui, vous l'avez bien lu, une fonction nommée avec une liste de longueur 2!), Alors elle s'appelle.

Sinon, la "expansion setf est générée", en utilisant la définition provenant de defsetf ou define-setf-expander.