Riferimenti variabili in lisp

Un’altra domanda LISP (comune) newbie:

Fondamentalmente nella maggior parte dei linguaggi di programmazione c’è un modo per le funzioni di ricevere riferimenti alle variabili anziché solo valori, cioè passando per riferimento invece che passando per valore. Diciamo, per semplicità, voglio scrivere una funzione LISP che riceve una variabile e aumenta il valore della variabile di uno:

(defun increase-by-one (var) (setf var (+ var 1))) 

Ora ovviamente il problema è che questa funzione aumenta solo il valore della copia della variabile sullo stack, non la variabile originale effettiva. Ho anche provato a ottenere l’effetto usando le macro senza molto successo, anche se ho la sensazione che usare le macro sia la strada giusta da percorrere.

Ho colpito questo muro tutto il tempo in LISP e sono sicuro che ci dev’essere un modo per aggirarlo o forse c’è un approccio completamente diverso a questo problema in LISP a cui non ho pensato? Come vengono fatte queste cose in LISP?

EDIT : più persone hanno suggerito di utilizzare incf . Ho solo usato questo esempio per dimostrare il problema in un modo semplice, in realtà non stavo cercando reimplementing incf. Ma grazie per i suggerimenti comunque.

    Con scope lessicale non si ha accesso a variabili che non si trovano nell’ambito corrente. Non è inoltre ansible passare direttamente variabili lessicali ad altre funzioni. Lisp valuta le variabili e passa i valori associati a queste variabili. Non c’è niente come riferimenti di prima class alle variabili.

    Pensa funzionale!

     (let ((a 1)) (values (lambda (new-value) (setf a new-value)) (lambda () a))) 

    sopra restituisce due funzioni. Si può leggere la variabile, un’altra può scrivere la variabile.

    Chiamiamo il primo writer funzioni e il secondo reader .

     (defun increase-by-one (writer reader) (funcall writer (1+ (funcall reader)))) 

    Quindi, per fare ciò che vuoi, il codice ha bisogno di a) essere nello scope o b) avere accesso alle funzioni che sono nello scope.

    Anche la variabile potrebbe essere globale .

     (defvar *counter* 1) (defun increase-by-one (symbol) (set symbol (1+ (symbol-value symbol)))) ; note the use of SET to set a symbol value (increase-by-one '*counter*) 

    Funziona per le variabili globali che sono rappresentate da un simbolo. Non funziona per le variabili lessicali – queste non sono rappresentate da un simbolo.

    C’è anche una macro INCF che aumenta un ‘luogo’ (per esempio una variabile).

     (incf a) 

    Ma a è la variabile nell’ambito corrente.

     (defun foo (a) (incf a)) ; increases the local variable a 

    Il limite è visto qui :

     (defun foo (var) (add-one-some-how var)) (let ((a 1)) (foo something-referencing-a)) 

    Non c’è modo di passare un riferimento diretto di a FOO .

    L’unico modo è quello di fornire una funzione. Dobbiamo anche riscrivere FOO , in modo che chiami la funzione fornita.

     (defun foo (f) (funcall f 1)) ; calls the function with 1 (let ((a 1)) (foo (lambda (n) (setf a (+ an))))) ;; passes a function to foo that can set a 

    Mentre Common Lisp supporta uno stile di programmazione funzionale, questo non è il suo objective generale (Scheme, sebbene non puramente funzionale, è molto più vicino). Common Lisp supporta molto bene uno stile di programmazione assolutamente imperativo.

    Se trovi che devi scrivere un codice del genere, la solita soluzione è una macro:

     (defmacro increase-by-one (var) `(setf ,var (+ ,var 1))) 

    Questo ti permette di scrivere codice come:

     (increase-by-one foo) 

    che sarà ampliato in:

     (setf foo (+ foo 1)) 

    prima che sia compilato.

    Naturalmente, in Lisp puoi fare a modo tuo fare riferimenti variabili, se vuoi. L’approccio più semplice è come questo:

     (defstruct reference getter setter) (defmacro ref (place) (let ((new-value (gensym))) `(make-reference :getter (lambda () ,place) :setter (lambda (,new-value) (setf ,place ,new-value))))) (defun dereference (reference) (funcall (reference-getter reference))) (defun (setf dereference) (new-value reference) (funcall (reference-setter reference) new-value)) 

    E poi puoi usarlo:

     (defun increase-by-one (var-ref) (incf (dereference var-ref))) (defun test-inc-by-one (n) (let ((mn)) (increase-by-one (ref m)) (values mn))) (test-inc-by-one 10) => 11, 10 

    Le macro sono probabilmente quelle che vuoi, perché non valutano i loro argomenti, quindi se passi un nome di variabile, ottieni un nome di variabile, non il suo valore.

    INCF fa esattamente quello che vuoi, quindi se usi google “defmacro incf” troverai una serie di definizioni per questo, alcune delle quali sono persino vicine all’essere corrette. 🙂

    Modifica: stavo suggerendo INCF non come alternativa alla scrittura personale, ma perché fa quello che vuoi, ed è una macro in modo da poter trovare facilmente il codice sorgente, ad esempio, ABCL o CMUCL .

    Penso che ti stia perdendo uno dei concetti chiave della programmazione funzionale: non devi cambiare lo stato degli oggetti una volta creati. Cambiare qualcosa tramite un riferimento viola questo.

    Come principiante sono venuto qui per capire come fare quella che dovrebbe essere una procedura banale in qualsiasi lingua. La maggior parte delle soluzioni pubblicate sopra non funzionava correttamente, poteva essere più complicata di quanto era necessario o implementazioni diverse. Ecco una soluzione semplice per SBCL :

     (defmacro inc-by-num (var num) (set var (+ (eval var) num))) 

    Apparentemente non è ansible utilizzare setf b / c, ma limita l’ambito mentre set no. Potrebbe anche essere necessario utilizzare eval prima di var se si ottiene un errore “argomento X non è un numero”.