2012-01-22 25 views
15

Qualcuno può spiegare il seguente comportamento? In particolare, perché la funzione restituisce ogni volta un elenco diverso? Perché non è some-list inizializzato su '(0 0 0) ogni volta che viene chiamata la funzione?Perché questa funzione restituisce un valore diverso ogni volta?

(defun foo() 
    (let ((some-list '(0 0 0))) 
    (incf (car some-list)) 
    some-list)) 

uscita:

> (foo) 
(1 0 0) 
> (foo) 
(2 0 0) 
> (foo) 
(3 0 0) 
> (foo) 
(4 0 0) 

Grazie!

EDIT:

Inoltre, qual è il metodo consigliato di implementare questa funzione, supponendo che io voglio la funzione di uscita '(1 0 0) ogni volta?

risposta

21

'(0 0 0) è un oggetto letterale, che si presume essere una costante (anche se non protetto da modifiche). Quindi stai modificando efficacemente lo stesso oggetto ogni volta. Per creare oggetti diversi per ciascuna funzione, utilizzare (list 0 0 0).

Quindi, a meno che non si sappia cosa si sta facendo, si dovrebbero sempre usare gli elenchi letterali (come '(0 0 0)) solo come costanti.

+0

Ah, ha senso ora. Grazie per la chiara spiegazione. –

+2

Probabilmente sarebbe bello aggiungere che anche a quasiquote non è garantito il ritorno di nuove liste. – 6502

+3

"a meno che tu non sappia, cosa stai facendo" Il comportamento di modifica dei dati letterali non è definito. Secondo le specifiche, non puoi sapere cosa stai facendo (con certezza), quindi "dovresti ** sempre ** usare liste letterali (come '(0 0 0)) solo come costanti". –

-5

voluto scrivere uno io, ma ho trovato una buona on-line:

Common Lisp ha funzioni di prima classe, vale a dire le funzioni sono oggetti che possono essere creati in fase di esecuzione, e passati come argomenti per altre funzioni. --AlainPicard Queste funzioni di prima classe hanno anche il loro stato, quindi sono funtori. Tutte le funzioni Lisp sono funtori; non esiste una separazione tra le funzioni "solo codice" e "funzione oggetti". Lo stato assume la forma di binding lessicale variabile binding. Non è necessario utilizzare LAMBDA per acquisire i binding; un defun di alto livello in grado di farlo anche: (let ((-variabile privata 42)) (defun foo() ...))

Il codice al posto di ... vede privato-variabile nel suo ambito lessicale . Esiste un'istanza di questa variabile associata a quella e all'oggetto funzione che è globalmente legato al simbolo FOO; la variabile viene acquisita nel momento in cui viene valutata l'espressione DEFUN. Questa variabile agisce quindi come una variabile statica in C. Oppure, alternativamente, si può pensare a FOO come un oggetto "singleton" con una variabile di istanza " ". --KazKylheku

Ref http://c2.com/cgi/wiki?CommonLisp

+1

Puoi spiegare come il testo che hai citato si riferisce alla domanda? Potrei mancare qualcosa, ma non lo vedo. – sepp2k

+0

Il testo spiega come le funzioni sono oggetti di prima classe in Lisp e in realtà hanno uno "stato". La variabile dichiarata faceva parte dello "stato" della funzione. Come spiega il testo, questo è molto simile alla dichiarazione di variabili locali statiche in C. Quale parte del testo non è correlata a questo problema? – xtrem

+2

La parte in cui non è affatto quello che sta accadendo. La tua citazione parla di "binding lessici variabili catturati". Comunque 'some-list' è una variabile locale di' foo', non è una variabile catturata e quindi non fa parte dello stato di 'foo'. Ad ogni invocazione di 'foo',' some-list' avrà un binding univoco (che come Vsevolod ha spiegato punterà alla stessa lista "costante", che spiega il comportamento dell'OP). Questo è completamente diverso da una funzione che modifica le variabili catturate. – sepp2k

9

Su un lato nota, definendo questa funzione nel REPL SBCL si ottiene il seguente avviso:

caught WARNING: 
    Destructive function SB-KERNEL:%RPLACA called on constant data. 
    See also: 
     The ANSI Standard, Special Operator QUOTE 
     The ANSI Standard, Section 3.2.2.3 

che dà un buon suggerimento verso il problema in questione.

4

'(0 0 0) nel codice è dati letterali. La modifica di questi dati ha un comportamento indefinito. Le implementazioni comuni di Lisp potrebbero non rilevarlo in fase di runtime (a meno che i dati non vengano posizionati ad esempio in uno spazio di memoria di sola lettura). Ma può avere effetti indesiderati.

  • si vede che questi dati può essere (e spesso è) in comune tra le varie invocazioni della stessa funzione

  • uno dei possibili errori più sottili è questo: Common Lisp è stato definito con varie ottimizzazioni che può essere fatto da un compilatore in mente. Ad esempio un compilatore è permesso di riutilizzare i dati:

Esempio:

(let ((a '(1 2 3)) 
     (b '(1 2 3))) 
    (list a b)) 

Nel codice precedente snippet il compilatore può rilevare che i dati letterali di a e b è EQUAL. Potrebbe quindi avere entrambe le variabili puntare agli stessi dati letterali. La modifica potrebbe funzionare, ma la modifica è visibile da a e b.

Sommario: La modifica dei dati letterali è una fonte di numerosi bug sottili. Evitalo se possibile. Quindi è necessario con nuovi oggetti dati. Consing in generale significa l'allocazione di nuove strutture dati nuove in fase di esecuzione.

Problemi correlati