Réinitialisation correcte d'une liste? Que se passe-t-il sous le capot?

8

Je m'apprends un peu plus et j'ai rencontré le problème suivant:

Si je veux réinitialiser une variable de liste, elle ne sera pas mise à jour après la première évaluation. Voici un exemple de code:

(defun initilize ()
  (setq example '(3)))

(defun modify ()
  (initilize)
  (message "%S" example)
  (setcar example 2))

; M-x eval-buffer RET
(modify) ; message --> (3)
(modify) ; message --> (2)
(modify) ; message --> (2)

Je m'intéresse à deux choses. La première consiste à en savoir plus sur ce qui se passe "sous le capot", alors pourquoi cela fonctionne-t-il la première fois et échoue lors des appels suivants?

La deuxième question, plus pratique, est de savoir comment réinitialiser correctement la liste ou existe-t-il une autre façon courante de faire quelque chose comme ça?

Une solution de contournement que je me suis trouvée consiste à utiliser une liste citée et à évaluer le contenu comme ceci:

(setq example `(,3)) 
clemera
la source
2
Résumé: Ne vous attendez pas '(some list)à être eqà '(some list)- jamais .Il est généralement aucune garantie dans ce code Lisp qui cite visiblement un rendement de liste nouvelle structure de la liste à chaque fois. Dans certaines implémentations Lisp, cela peut ou peut parfois arriver. Dans d'autres, ce n'est jamais le cas. Votre code ne devrait de toute façon pas dépendre d'un tel comportement de l'implémentation. Si vous voulez une nouvelle structure de liste, utilisez listou consou équivalent.
Drew
(Je suppose que cette question est un doublon, mais je ne sais pas où se trouve le doublon.)
Drew
1
Je pense que le problème ici est qu'il examplen'a jamais été déclaré en tant que variable, il setqdoit donc agir comme s'il déclarait une nouvelle variable, mais plus tard, lorsque vous appelez à initializenouveau, une nouvelle variable est en cours de création, tout en se modifysouvenant de l'ancienne ... dans tous les cas, ce n'est pas un comportement attendu, cependant, l'utilisation de setqavec quelque chose qui n'a pas été introduit plus tôt comme variable pourrait tout aussi bien être indéfinie.
wvxvw
3
OK, j'ai compris ce qui se passe. '(3)est traité comme une valeur littérale, donc une fois que vous (setcar '(3) 2), quand vous le ferez (defvar foo '(3))ou (let ((foo '(3)))ainsi de suite, vous obtiendrez probablement une valeur fooégale à '(2). Je dis "probable" parce que ce comportement n'est pas garanti, c'est une sorte d'optimisation que l'interprète fait chaque fois qu'il le souhaite, quelque chose appelé élimination de la sous-expression des constantes (un cas particulier de). Donc, ce qu'abo-abo a écrit n'est pas exactement la raison. Cela ressemble plus à la modification d'un littéral de chaîne en C (ce qui génère généralement un avertissement).
wvxvw
2
Duplicate de stackoverflow.com/q/16670989
phils

Réponses:

5

Peut-être que cela dissipera une partie de la confusion:

  • Votre fonction initilizen'initialise pas la variable example. Il le définit sur une cellule de contre particulière - la même cellule de contre chaque fois qu'il est appelé. La première fois que l' initilizeon appelle, l' setqassigne exampleà une nouvelle cellule contre, qui est le résultat de l'évaluation '(3). Appels ultérieurs pour initilizesimplement réaffecter exampleà la même cellule de contre.

  • Comme il initilizesuffit de réaffecter la même cellule de contre à example, il modifysuffit de définir la voiture de cette même cellule de contre à 2chaque appel.

  • Pour initialiser une liste, utilisez listou cons(ou un sexp équivalent de backquote, tel que `(,(+ 2 1))ou `(,3)). Par exemple, utilisez (list 3).

La clé pour comprendre cela est de savoir qu'une cellule contre citée n'est évaluée qu'une seule fois, puis la même cellule contre est retournée. Ce n'est pas nécessairement la façon dont tous les Lisps se comportent, mais c'est la façon dont Emacs Lisp se comporte.

Plus généralement, le comportement de l'évaluation d'un objet mutable cité dépend de l'implémentation, sinon de la langue. En Common Lisp, par exemple, je suis quasiment sûr qu'il n'y a rien dans la définition (spec) du langage qui définit le comportement à cet égard - c'est laissé à l'implémentation.

Résumé: Ne vous attendez pas à ce que '(une liste) soit égal à' (une liste) - jamais. Il n'y a généralement aucune garantie dans Lisp que le code qui cite visiblement une liste renvoie à chaque fois une nouvelle structure de liste. Dans certaines implémentations Lisp, cela peut ou peut parfois arriver. Dans d'autres, ce n'est jamais le cas. Votre code ne devrait de toute façon pas dépendre d'un tel comportement de l'implémentation. Si vous voulez une nouvelle structure de liste, utilisez listou consou équivalent.

A dessiné
la source
1
Le ton condescendant des deux premières lignes de votre réponse est-il nécessaire? Sinon, une réponse éclairante.
asjo
@asjo: L'intention n'était pas de condescendre en aucune façon; Désolé. J'espère que c'est plus clair maintenant.
Tiré
Merci d'éclaircir un peu les choses. Avec le terme "argument", je voulais dire l'argument `(, 3) pour la fonction setq, bien que je comprenne qu'il est un peu flou parce que j'évalue vraiment le 3 dans la liste citée. Je vais éditer ça.
clemera
@Drew Great! Il est plus convivial de lire maintenant.
asjo
5

Vous pouvez utiliser (setq example (list 3))pour éviter cette erreur.

Ce qui se passe, c'est initattribue un objet qui contient initialement (3)à example. Il définit la valeur de l'objet une seule fois. Par la suite, vous modifiez cette valeur.

Voici votre exemple en C ++, si vous le comprenez mieux:

#include <stdio.h>
#include <string.h>
char* example;
char* storage = 0;
char* create_object_once (const char* str) {
  if (storage == 0) {
    storage = new char[strlen (str)];
    strcpy (storage, str);
  }
  return storage;
}
void init () {
  example = create_object_once ("test");
}
void modify () {
  init ();
  printf ("%s\n", example);
  example[0] = 'f';
}
int main (int argc, char *argv[]) {
  modify ();
  modify ();
  modify ();
  return 0;
}
abo-abo
la source
1
Merci, je ne connais pas le C ++ mais je comprends votre exemple. Cependant, une chose me dérange encore: dans votre exemple de code, vous n'introduisez la variable de stockage qui crée l'objet avec la valeur initiale que s'il n'a pas encore été défini. Comment cela se traduit-il par ce que fait l'Emacs Lisp Interpreter? Je veux dire que si je réévalue la initilizefonction et que j'appelle à modifynouveau, elle ne réapparaîtra (3)que parce que j'ai réévalué la fonction.
clemera
1
Je ne peux pas entrer dans les détails (je ne les connais pas exactement), mais je pense (3)à un objet temporaire qui en fait partie init. initLe corps de 's définit exampleà l'adresse de cet objet temporaire, il ne touche pas sa valeur.
abo-abo