Waarom verandert een constante in `let` met herhaalde oproepen?

Stel dat we een dergelijke foo -functie hebben:

(defun foo (e)
  (let ((lst '(a b c)))
    (delq e lst)))

Vervolgens gebruiken we het op de volgende manier (opeenvolgend een voor een evalueren):

(foo 'c) ; => (a b)
(foo 'b) ; => (a)
(foo 'a) ; => nil
(foo 'b) ; => (a)

Wat is hier gebeurd?

Als ik een andere functie bar definieer:

(defun bar (e)
  (let ((lst (list 'a 'b 'c)))
    (delq e lst)))

Gebruik het dan op dezelfde manier:

(bar 'c) ; => (a b)
(bar 'b) ; => (a c)
(bar 'a) ; => (b c)
(bar 'b) ; => (a c)

De resultaten hier lijken mij redelijk.

Dus de vraag is, waarom de constante in de let -vorm van de functie foo zich op deze manier gedraagt? Is het bedoeld?

11
toegevoegd de auteur shabbychef, de bron
Een andere belangrijke aanhalingsteken gemarkeerd in het duplicaat op S.O. is dit (dat is uit Ch f quote ): "Waarschuwing: quote construeert zijn retourwaarde niet, maar retourneert alleen de waarde die vooraf door de Lisp-lezer was geconstrueerd ". Ik denk dat nieuwkomers om te lispelen zich vaak niet bewust zijn van het hele concept van de lisp-lezer, en dus het enige tijd nemen om het onderscheid tussen de "lees" en "eval" -stadia te leren en te begrijpen, is erg nuttig voor het begrijpen van de taal in het algemeen, zoals en specifieke problemen zoals die hierboven.
toegevoegd de auteur Mark Ireland, de bron
Strikt genomen is ' geen lezer-macro in elisp (elisp heeft geen leesmacro's), maar het wordt zeker verwerkt tijdens de leesfase. Dat is echter niet het belangrijkste. Mijn punt ging over het begrijpen dat de lezer alle tekstuele lisp-code omzet in lisp-objecten (eenmaal), en het zijn die objecten die vervolgens (vaak herhaaldelijk) worden geëvalueerd in plaats van de tekst die je ziet.
toegevoegd de auteur Mark Ireland, de bron
En omdat objecten mogelijk tijdens de evaluatie aan manipulatie onderhevig zijn (zoals in deze vraag), hoeft niet </> om de oorspronkelijk gelezen code weer te geven wat er op enig moment wordt geëvalueerd. Natuurlijk zal het in de meeste gevallen precies dat doen - je hoeft niet vaak te denken in termen van objecten. Maar als je weet wat er achter de schermen gebeurt, kun je de situaties waarin dit soort dingen een rol spelen herkennen en begrijpen.
toegevoegd de auteur Mark Ireland, de bron
@phils Bedoel je dat ' een lezermacro is, die wordt verwerkt in de "lees" -fase, terwijl (lijst' a 'b' c) wordt geëvalueerd in de " eval "stadium?
toegevoegd de auteur Mahesh, de bron
@phils Bedankt! Dat is logisch.
toegevoegd de auteur Mahesh, de bron

1 antwoord

Hier is mijn antwoord op de identieke vraag , op de juiste manier bewerkt:

De slechte

foo is self-modifying code. This is extremely dangerous. While the variable lst disappears at the end of the let form, its initial value persists in the function object, and that is the value you are modifying. Remember that in Lisp a function is a first class object, which can be passed around (just like a number or a list), and, sometimes, modified. This is exactly what you are doing here: the initial value for lst is a part of the function object and you are modifying it.

Laten we eens kijken wat er aan de hand is:

(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a b c)))) (delq e lst)))
(foo 'c)
==> (a b)
(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a b)))) (delq e lst)))
(foo 'b)
==> (a)
(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a)))) (delq e lst)))
(foo 'a)
==> nil
(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a)))) (delq e lst)))

De goede

In bar wordt lst geïnitialiseerd naar een nieuwe cons-cel en is dus veilig. bar verandert niet de code.

Het komt neer op

Over het algemeen is het het beste om geciteerde gegevens te behandelen zoals '(1) als constanten - doe niet bewerk ze:

quote retourneert het argument, zonder het te evalueren. (quote x) geeft x .    Waarschuwing : quote construeert de retourwaarde niet, maar retourneert alleen   de waarde die vooraf was geconstrueerd door de Lisp-lezer (zie informatieknooppunt    Printed Representation ). Dit betekent dat (a. B) dat niet is   identiek aan (nadelen 'a' b) : de eerste heeft geen nadelen. Citeren zou moeten   gereserveerd voor constanten die nooit zullen worden aangepast door bijwerkingen,   tenzij je van zelfmodificerende code houdt. Zie de gebruikelijke valkuil in info   knoop Herinrichting voor een voorbeeld van onverwachte resultaten wanneer   een aangehaald object is gewijzigd.

Als u een lijst wilt bewerken , maak het met lijst of nadelen of kopieerlijst in plaats van citaat , zoals u doet in bar .

Zie more voorbeelden .

ps - variabele naamgeving

Emacs Lisp is een lisp-2 , dus dit is geen reden om uw variabele lst te noemen in plaats van lijst .

18
toegevoegd
ja dat is erg verwarrend! Maar ook echt interessant, ik had geen idee van lisp-1 nad lisp-2s.
toegevoegd de auteur Jeff, de bron
Bedankt voor de gedetailleerde uitleg! Ik weet ook dat ELisp lisp-2 is, maar toch wil ik mezelf niet in verwarring brengen. Iets als (lijst (lijst 'a' b 'c)) (hoewel het zich in het laat -formulier bevindt) ziet er op het eerste gezicht erg verwarrend uit.
toegevoegd de auteur Mahesh, de bron