¿Asignar el mismo valor a múltiples variables?

12

A veces necesito establecer el mismo valor para múltiples variables.

En Python, podría hacer

f_loc1 = f_loc2 = "/foo/bar"

Pero en elisp, estoy escribiendo

(setq f_loc1 "/foo/bar" f_loc2 "/foo/bar")

Me pregunto si hay una manera de lograr esto usando "/foo/bar"solo una vez.

ChillarAnand
fuente
1
Hola, Chillar, ¿puedes seleccionar la respuesta de @tarsius como la aceptada? Mi respuesta demuestra una solución que te da lo que quieres, pero como dije es "ejercicio de una especie" que resuelve este desafío posiblemente artificial pero no muy útil en la práctica. tarsuis ha puesto mucho esfuerzo en demostrar esta consideración obvia en su respuesta, por lo que creo que su respuesta merece ser "la correcta", por lo que todos están felices de nuevo.
Mark Karpov
1
Argumentaría que la necesidad de asignar el mismo valor a múltiples variables indica un defecto de diseño que debería
corregirse en
1
@lunaryorn si desea establecer sourcey targeten la misma ruta al principio y podría cambiar targetmás tarde, ¿cómo configurarlos?
ChillarAnand
Mostrar más código, para que podamos decir qué quiere decir con "variable" para su caso de uso; es decir, qué tipo de variables está tratando de establecer. Vea mi comentario para la respuesta de @ JordonBiondo.
Dibujó el

Respuestas:

21

setq devuelve el valor, por lo que puede simplemente:

(setq f-loc1 (setq f-loc2 "/foo/bar"))

Si no quieres confiar en eso, usa:

(setq f-loc1 "/foo/bar" f-loc2 f-loc1)

Personalmente, evitaría lo último y en su lugar escribiría:

(setq f-loc1 "/foo/bar"
      f-loc2 f-loc1)

o incluso

(setq f-loc1 "/foo/bar")
(setq f-loc2 f-loc1)

Y el primer enfoque solo lo usaría en circunstancias bastante especiales donde realmente enfatiza la intención, o cuando la alternativa sería usar el segundo enfoque o de lo contrario progn.


Puedes dejar de leer aquí si lo anterior te hace feliz. Pero creo que esta es una buena oportunidad para advertirle a usted y a otros que no abusen de las macros.


Finalmente, aunque es tentador escribir una macro como setq-every"porque esto es lisp y podemos", recomendaría encarecidamente que no lo haga. Ganas muy poco al hacerlo:

(setq-every value a b)
   vs
(setq a (setq b value))

Pero al mismo tiempo, hace que sea mucho más difícil para otros lectores leer el código y asegurarse de lo que sucede. setq-everypodría implementarse de varias maneras y otros usuarios (incluido un futuro que usted) no pueden saber cuál elige el autor, simplemente mirando el código que lo usa. [Originalmente hice algunos ejemplos aquí, pero alguien los consideró sarcásticos y despotricaron].

Puedes lidiar con esa sobrecarga mental cada vez que miras setq-everyo puedes vivir con un solo personaje adicional. (Al menos en el caso de que tenga que establecer solo dos variables, pero no puedo recordar que alguna vez tuve que establecer más de dos variables al mismo valor a la vez. Si tiene que hacer eso, entonces probablemente haya algo más mal con tu código)

Por otro lado, si realmente va a utilizar esta macro más de media docena de veces, entonces la indirección adicional podría valer la pena. Por ejemplo, uso macros como --when-letde la dash.elbiblioteca. Eso hace que sea más difícil para quienes lo encuentran por primera vez en mi código, pero vale la pena superar esa dificultad de comprensión porque se usa más que solo unas pocas veces y, al menos en mi opinión, hace que el código sea más legible .

Se podría argumentar que lo mismo se aplica setq-every, pero creo que es muy poco probable que esta macro en particular se use más de unas pocas veces. Una buena regla general es que cuando la definición de la macro (incluida la cadena de documentos) agrega más código del que elimina el uso de la macro, es muy probable que la macro sea exagerada (lo contrario no es necesariamente cierto).

tarsius
fuente
1
después de configurar una var setq, puede hacer referencia a su nuevo valor, por lo que también podría usar esta monstruosidad: (setq a 10 b a c a d a e a)que establece abcd ye en 10.
Jordon Biondo
1
@JordonBiondo, sí, pero creo que el objetivo de OP es reducir la repetición en su código :-)
Mark Karpov
3
¡Me gustaría darle un +10 por la advertencia contra el abuso macro!
lunaryorn
Acabo de crear una búsqueda sobre las mejores prácticas macro. emacs.stackexchange.com/questions/21015 . Espero que @tarsius y otros den una gran respuesta.
Yasushi Shoji
13

Una macro para hacer lo que quieras

Como ejercicio de una especie:

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE."
  `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars)))

Ahora pruébalo:

(setq-every "/foo/bar" f-loc1 f-loc2)

Como funciona

Como la gente siente curiosidad por cómo funciona (según los comentarios), aquí hay una explicación. Para aprender realmente cómo escribir macros, elija un buen libro de Common Lisp (sí, Common Lisp, podrá hacer lo mismo en Emacs Lisp, pero Common Lisp es un poco más poderoso y tiene mejores libros, en mi humilde opinión).

Las macros funcionan con código sin formato. Las macros no evalúan sus argumentos (a diferencia de las funciones). Así que aquí tenemos una evaluación sin evaluar valuey varsque, para nuestra macro, son solo símbolos.

prognagrupa varias setqformas en una. Esta cosa:

(mapcar (lambda (x) (list 'setq x value)) vars)

Simplemente genera una lista de setqformularios, usando el ejemplo de OP será:

((setq f-loc1 "/foo/bar") (setq f-loc2 "/foo/bar"))

Verá, el formulario está dentro del formulario de comillas invertidas y tiene como prefijo una coma ,. Dentro de la forma retrocomillada, todo se cita como de costumbre, pero la , evaluación "se enciende" temporalmente, por lo que la totalidad mapcarse evalúa en el tiempo de macroexpansión.

Finalmente @elimina el paréntesis externo de la lista con setqs, por lo que obtenemos:

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Las macros pueden transformar arbitrariamente su código fuente, ¿no es genial?

Una advertencia

Aquí hay una pequeña advertencia, el primer argumento se evaluará varias veces, porque esta macro se expande esencialmente a lo siguiente:

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Verá, si tiene una variable o cadena aquí está bien, pero si escribe algo como esto:

(setq-every (my-function-with-side-effects) f-loc1 f-loc2)

Entonces su función se llamará más de una vez. Esto puede ser indeseable. Aquí se explica cómo solucionarlo con la ayuda de once-only(disponible en el paquete MMT ):

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE.

VALUE is only evaluated once."
  (mmt-once-only (value)
    `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars))))

Y el problema se ha ido.

Mark Karpov
fuente
1
Sería bueno si se puede añadir algunas explicaciones de cómo funciona esto y lo que este código está haciendo con más detalle, para que la gente próxima vez puede escribir algo como esto ellos mismos :)
clemera
No desea evaluar valueen el momento de la macroexpansión.
tarsius
@tarsius, yo no: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Si valuese evaluaran en el tiempo de macroexpansión, se sustituiría con una cadena real. Puede colocar la llamada de función con efectos secundarios, en lugar de value, no se evaluará hasta que se ejecute el código expandido, pruébelo. valuecomo argumento de macro no se evalúa automáticamente. El problema real es que valuese evaluará varias veces, lo que puede ser indeseable, once-onlypuede ayudar aquí.
Mark Karpov
1
En lugar de mmt-once-only(que requiere una dep externa), podría usar macroexp-let2.
YoungFrog
2
Ugh La pregunta grita para usar la iteración con set, no para envolver setqen una macro. O simplemente utilícelo setqcon múltiples argumentos, incluida la repetición de argumentos.
Drew
7

Dado que puede usar y manipular símbolos en lisp, simplemente puede recorrer una lista de símbolos y usarlos set.

(dolist (var '(foo bar baz)) (set var 10))

(mapc (lambda (var) (set var 10)) '(foo bar baz))

(loop for var in '(foo bar baz) do (set var 11))

(--each '(foo bar baz) (set it 10))
Jordon Biondo
fuente
2
Sin embargo, esto no funciona para las variables unidas léxicamente
npostavs
@npostavs: Cierto, pero podría ser lo que el OP quiere / necesita. Si no está usando variables léxicas, definitivamente es el camino a seguir. Sin embargo, tiene razón, esa "variable" es ambigua, por lo que, en general, la pregunta podría significar cualquier tipo de variable. El caso de uso real no está bien presentado, OMI.
Dibujó el