2009-08-08 9 views
22

Otro novato (Común) pregunta LISP:referencias variables en Lisp

Básicamente en la mayoría de los lenguajes de programación que hay una media de funciones para recibir referencias a variables en lugar de sólo los valores, es decir, el paso por referencia en lugar de pasar por valor . Digamos que, en aras de la simplicidad, quiero escribir una función LISP que recibe una variable y aumenta el valor de la variable por uno:

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

Ahora, evidentemente, el problema es que esta función sólo aumenta el valor de la copia de la variable en la pila, no la variable original real. También intenté lograr el efecto mediante el uso de macros sin mucho éxito, aunque tengo la sensación de que usar macros es el camino correcto.

Llegué a esta pared todo el tiempo en LISP y estoy seguro de que debe haber una forma de evitarlo o tal vez hay un enfoque completamente diferente para este problema en LISP en el que no había pensado. ¿Cómo se hacen cosas como esta en LISP?

EDIT: Varias personas han sugerido usar incf. Solo utilicé este ejemplo para demostrar el problema de una manera simple, no estaba buscando reimplementar incf. Pero gracias por las sugerencias de todos modos.

+3

Dice "la mayoría de los lenguajes de programación", pero ¿hay idiomas con características que no sean C++ y Perl? –

Respuesta

17

Con el alcance léxico uno no tiene acceso a las variables que no están en el alcance actual. Tampoco puede pasar variables léxicas a otras funciones directamente. Lisp evalúa variables y pasa los valores vinculados a estas variables. No hay nada como las referencias de primera clase a las variables.

Think functional!

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

anterior devuelve dos funciones. Uno puede leer la variable, otro puede escribir la variable.

Llamemos a la primera función writer y la segunda reader.

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

lo tanto, para hacer lo que quiere, necesita el código a) estar en el alcance o b) tener acceso a las funciones que se encuentran en el alcance.

También la variable podría ser global.

(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*) 

Esto funciona para las variables globales que están representadas por un símbolo. No funciona para variables léxicas; estas no están representadas por un símbolo.

También hay una macro INCF que aumenta un 'lugar' (por ejemplo, una variable).

(incf a) 

Pero a es la variable en el ámbito actual.

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

El límite se ve aquí:

(defun foo (var) 
    (add-one-some-how var)) 

(let ((a 1)) 
    (foo something-referencing-a)) 

No hay manera de pasar una referencia directa de a a FOO.

La única forma es proporcionar una función:

(defun foo (f) 
    (funcall f 1)) ; calls the function with 1 

(let ((a 1)) 
    (foo (lambda (n) 
      (setf a (+ a n))))) 
    ;; passes a function to foo that can set a 
+0

Gran explicación, ayudó Mi mucho. ¡Gracias! –

0

Creo que se está perdiendo uno de los conceptos clave de la programación funcional: no se supone que deba cambiar el estado de los objetos una vez que se hayan creado. Cambiar algo a través de una referencia viola eso.

+0

Es suficiente, supongo. –

+0

destrucción es común en la programación – Gutzofter

+8

No debe suponer que uno ** debe ** programar en un estilo funcional en Common Lisp. Una de las cosas buenas de esto es que es multi-paradigma y no te obliga a un estilo de programación. Si desea ver un ejemplo de dónde este hecho se usa explícitamente para enmarcar un texto introductorio al idioma, consulte "Common Lisp: Una Introducción Suave a la Computación Simbólica" de D. Touretsky. – Pinochle

7

Mientras Common Lisp admite un estilo de programación funcional, que no es su enfoque general (Scheme, aunque no es puramente funcional, está mucho más cerca). Common Lisp soporta muy bien un estilo de programación completamente imperativo.

Si usted encuentra que necesita para escribir código de esa manera, la solución habitual es una macro:

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

Esto le permite escribir código como:

(increase-by-one foo) 

que será ampliado en:

(setf foo (+ foo 1)) 

antes de compilarlo.

+8

Una mejor solución es usar INCF, particularmente porque su INSCREASE-BY-ONE tiene errores y evalúa VAR dos veces. –

+0

Ese es un buen punto, mi ejemplo sería una mejor ilustración si usa 'setq' en lugar de' setf'. La macro 'setf' tiene todo un mundo de código detrás para soportar variables de lugar generalizadas, por lo que la macro anterior 'increase-by-one' es inapropiada para usar con' setf'. –

+0

@ Luís Oliveira: Sí, es por eso que también me sorprendió el enfoque macro. Aunque no puedo ver cómo evaluar un variable dos veces podría ser un problema. Quiero decir, no es una expresión, es solo una variable de todos modos. ¿O extraño algo? –

6

Por supuesto, en Lisp puede hacer su propio camino hacen referencias a variables, si así lo desea. El enfoque más simple es la siguiente:

(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)) 

Y entonces usted puede usarlo:

(defun increase-by-one (var-ref) 
    (incf (dereference var-ref))) 

(defun test-inc-by-one (n) 
    (let ((m n)) 
    (increase-by-one (ref m)) 
    (values m n))) 

(test-inc-by-one 10) => 11, 10 
1

macros son probablemente lo que quiere, porque no evalúan sus argumentos, por lo que si se pasa una variable nombre, obtienes un nombre de variable, no su valor.

INCF hace exactamente lo que desea, por lo que si busca en google "defmacro incf" encontrará un montón de definiciones para esto, algunas de las cuales están cerca de ser correctas. :-)

Editar: Yo no estaba sugiriendo INCF como una alternativa a escribir su propia, sino porque hace lo que quiere, y es una macro para que pueda encontrar fácilmente el código fuente para ello, por ejemplo, o ABCLCMUCL.

+1

¡Gracias! Sabía de incf por cierto, esto era solo un ejemplo para demostrar el problema, por supuesto, no estaba tratando de volver a implementar incf. –

-3

Como principiante, vine aquí para descubrir cómo hacer lo que debería ser un procedimiento trivial en cualquier idioma. La mayoría de las soluciones publicadas anteriormente no funcionaron correctamente, pudieron haber sido más complicadas de lo que se necesitaban, o diferentes implementaciones. Aquí es una solución sencilla para SBCL:

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

Al parecer, no se puede utilizar setf b/c que restringe el alcance mientras que set no. También es posible que necesite usar eval antes de var si obtiene un error "argumento X no es un número".

+0

La solución de Greg es mucho mejor, no se encuentra con riesgos extraños al llamar a eval todo el tiempo ... –

+0

No. Esto no funciona como podría pensar. No tengo el tiempo y el lugar aquí para mostrar los intrincados mecanismos que hacen que creas que se logra el efecto que deseas, así que solo daré una pista: la configuración aquí tiene lugar en el momento de la macro expansión. – Svante