6

Sto provando a passare un elenco a una funzione in Lisp e modificare il contenuto di tale elenco all'interno della funzione senza influire sull'elenco originale. Ho letto che Lisp è un valore pass-by, ed è vero, ma c'è qualcos'altro che non capisco. Ad esempio, questo codice funziona come previsto:In Common-Lisp, come posso modificare parte di un parametro di elenco da una funzione senza modificare l'elenco originale?

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

Se si chiama (test), la stampa (a b c) anche se (modifica) restituisce (x y z).

Tuttavia, non funziona in questo modo se si tenta di modificare solo una parte dell'elenco. Presumo che questo abbia qualcosa a che fare con liste che hanno lo stesso contenuto uguale in memoria ovunque o qualcosa del genere? Ecco un esempio:

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

Quindi (prova) stampa (x b c). Quindi, come posso modificare alcuni elementi di un parametro di elenco in una funzione, come se quell'elenco fosse locale per quella funzione?

+3

Si noti che le conseguenze della modifica delle costanti letterali non sono definite. Non farlo. Mai. '(a b c) è una costante letterale nel codice. Non dovresti modificarlo. È possibile modificare gli elenchi creati con la funzione LIST come (lista 'a' b 'c). –

+4

notare inoltre che (SETF originale '(a b c)) non ha senso. SETF non introduce variabili.la variabile 'originale' non è definita da nessuna parte. Puoi impostare le variabili che sono state introdotte tramite LET, DEFUN, DEFVAR, DEFPARAMETER, ... –

risposta

7

SETF modifica un posto. n può essere un posto. Il primo elemento della lista n punti possono anche essere posto.

in entrambi i casi, l'elenco tenuto da original viene passato come parametro modifyn. Ciò significa che sia original nella funzione test e n nella funzione modify ora tenere la stessa lista, che significa che sia original sia n puntano ora al suo primo elemento.

Dopo che SETF modifica n nel primo caso, non punta più a tale elenco ma a un nuovo elenco. L'elenco indicato da original non è interessato. Il nuovo elenco viene quindi restituito da modify, ma dal momento che questo valore non è assegnato a nulla, scompare dalla sua esistenza e sarà presto raccolto.

Nel secondo caso, SETF modifica non n, ma il primo elemento dell'elenco n indica. Questo è lo stesso elenco original punti, quindi, in seguito, è possibile visualizzare l'elenco modificato anche attraverso questa variabile.

Per copiare un elenco, utilizzare COPY-LIST.

2

Probabilmente hai problemi perché anche se Lisp è un riferimento pass-by agli oggetti, come in Java o Python. Le tue caselle di controllo contengono riferimenti che modifichi, quindi puoi modificare sia quella originale che quella locale.

IMO, dovresti provare a scrivere le funzioni in uno stile più funzionale per evitare tali problemi. Anche se Common Lisp è multi-paradigma, uno stile funzionale è un modo più appropriato.

(defun modificare (n) (x cons'(CDR n))

+0

Questo è il mio primo progetto in lisp, e non capisco ancora la programmazione funzionale. Immagino che devo solo ripensare al modo in cui ho strutturato il mio programma? Sto cercando di esaminare le potenziali modifiche a uno stato di gioco senza modificare lo stato. Quindi, invece di creare funzioni per makechange e undochange, speravo di poter modificare una "copia" dello stato. – Ross

+0

In genere è consigliabile evitare modifiche di stato non necessarie. Spesso non hai bisogno di loro e ti fanno programmare più difficile eseguire il debug e portare a errori come questo. – freiksenet

11

Gli elenchi di Lisp sono basati su celle cons. Le variabili sono come puntatori alle celle contro (o altri oggetti Lisp). Cambiare una variabile non cambierà altre variabili. Cambiando le contro cellule sarà visibile in tutti i posti in cui ci sono riferimenti a quelle contro le cellule.

Un buon libro è Touretzky, Common Lisp: A Gentle Introduction to Symbolic Computation.

C'è anche un software che disegna alberi di elenchi e celle di controllo.

Se si passa un elenco per una funzione come questa:

(modify (list 1 2 3)) 

Poi ci sono tre modi diversi di utilizzare l'elenco:

modifica distruttiva delle cellule cons

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

condivisione struttura

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

Sopra restituisce una lista che condivide la struttura con la lista passata: gli elementi rest sono gli stessi in entrambi gli elenchi.

copia

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

Sopra funzione BAZ è simile al bar, ma non le cellule lista sono condivisi, in quanto l'elenco viene copiato.

Inutile dire che spesso le modifiche distruttive dovrebbero essere evitate, a meno che non ci sia un vero motivo per farlo (come risparmiare memoria quando ne vale la pena).

Note:

mai distruttivo modificare letterali liste costanti

Dont 'fare: (let ((l' (ABC))) (setf (prima l) 'bar))

Motivo: l'elenco può essere protetto da scrittura, oppure può essere condiviso con altri elenchi (organizzato dal compilatore), ecc

anche:

Introdurre variabili

come questo

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

o come questo

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

mai SETF una variabile non definita.

+0

Grazie. Questo è stato molto utile per me. – Ross

4

è quasi lo stesso di questo esempio in C:

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

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

in entrambi i casi un puntatore è passato, se si cambia il puntatore, stai cambiando la copia del parametro del valore di puntatore (che è su lo stack), se cambi il contenuto, stai cambiando il valore di qualsiasi oggetto puntato.

Problemi correlati