En common-lisp, comment modifier une partie d'un paramètre de liste depuis une fonction sans changer la liste d'origine ?

StackOverflow https://stackoverflow.com/questions/1484335

Question

J'essaie de transmettre une liste à une fonction en Lisp et de modifier le contenu de cette liste dans la fonction sans affecter la liste d'origine.J'ai lu que Lisp passe par valeur, et c'est vrai, mais il se passe autre chose que je ne comprends pas très bien.Par exemple, ce code fonctionne comme prévu :

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf n '(x y z))
    n)

Si vous appelez (test), il imprime (ab c) même si (modify) renvoie (x y z).

Cependant, cela ne fonctionne pas de cette façon si vous essayez de modifier seulement une partie de la liste.Je suppose que cela a quelque chose à voir avec le fait que les listes qui ont le même contenu sont les mêmes en mémoire partout ou quelque chose comme ça ?Voici un exemple:

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf (first n) 'x)
    n)

Puis (test) imprime (x b c).Alors, comment puis-je modifier certains éléments d'un paramètre de liste dans une fonction, comme si cette liste était locale à cette fonction ?

Était-ce utile?

La solution

SETF modifie un lieu. n peut être un lieu.Le premier élément de la liste n les points vers peuvent aussi être un lieu.

Dans les deux cas, la liste détenue par original est transmis à modify comme paramètre n.Cela signifie que les deux original dans la fonction test et n dans la fonction modify détiennent désormais la même liste, ce qui signifie que les deux original et n pointons maintenant vers son premier élément.

Après la modification de SETF n dans le premier cas, il ne pointe plus vers cette liste mais vers une nouvelle liste.La liste indiquée par original n’est pas affecté.La nouvelle liste est ensuite renvoyée par modify, mais comme cette valeur n'est attribuée à rien, elle disparaît et sera bientôt récupérée.

Dans le deuxième cas, SETF ne modifie pas n, mais le premier élément de la liste n pointe vers.C'est la même liste original pointe vers, donc, par la suite, vous pouvez également voir la liste modifiée via cette variable.

Pour copier une liste, utilisez COPIER-LISTE.

Autres conseils

Les listes Lisp sont basées sur des cellules négatives.Les variables sont comme des pointeurs vers des cellules négatives (ou d'autres objets Lisp).Changer une variable ne changera pas les autres variables.La modification des cellules contre sera visible à tous les endroits où il y a des références à ces cellules contre.

Un bon livre est Touretzky, Lisp commun :Une introduction douce au calcul symbolique.

Il existe également des logiciels qui dessinent des arbres de listes et de contre-cellules.

Si vous transmettez une liste à une fonction comme celle-ci :

(modify (list 1 2 3))

Ensuite, vous avez trois manières différentes d'utiliser la liste :

modification destructrice des contre-cellules

(defun modify (list)
   (setf (first list) 'foo)) ; This sets the CAR of the first cons cell to 'foo .

partage de structure

(defun modify (list)
   (cons 'bar (rest list)))

Ci-dessus renvoie une liste qui partage la structure avec la liste transmise :les autres éléments sont les mêmes dans les deux listes.

copier

(defun modify (list)
   (cons 'baz (copy-list (rest list))))

La fonction ci-dessus BAZ est similaire à BAR, mais aucune cellule de liste n'est partagée, puisque la liste est copiée.

Inutile de dire que les modifications destructives doivent souvent être évitées, à moins qu'il n'y ait une réelle raison de le faire (comme économiser de la mémoire lorsque cela en vaut la peine).

Remarques:

ne modifiez jamais de manière destructive les listes de constantes littérales

Ne faites pas :(let ((l '(a b c))) (setf (premier l) 'bar))

Raison:la liste peut être protégée en écriture ou partagée avec d'autres listes (organisées par le compilateur), etc.

Aussi:

Introduire des variables

comme ça

(let ((original (list 'a 'b 'c)))
   (setf (first original) 'bar))

ou comme ça

(defun foo (original-list)
   (setf (first original-list) 'bar))

jamais SETF une variable non définie.

c'est presque la même chose que cet exemple en C :

void modify1(char *p) {
    p = "hi";
}

void modify2(char *p) {
    p[0] = 'h';
}

dans les deux cas, un pointeur est passé, si vous modifiez le pointeur, vous modifiez la copie du paramètre de la valeur du pointeur (c'est-à-dire qu'il se trouve sur la pile), si vous modifiez le contenu, vous modifiez la valeur de l'objet pointé. .

Vous avez probablement des problèmes car même si Lisp est passé par valeur, les références aux objets sont transmises, comme en Java ou Python.Vos contre-cellules contiennent des références que vous modifiez, vous modifiez donc à la fois celle d'origine et celle locale.

OMI, vous devriez essayer d'écrire des fonctions dans un style plus fonctionnel pour éviter de tels problèmes.Même si Common Lisp est multi-paradigme, un style fonctionnel est une méthode plus appropriée.

(Defun modify (n) (Cons 'x (cdr n))

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top