Attribuer la même valeur à plusieurs variables?

12

Parfois, je dois définir la même valeur sur plusieurs variables.

En Python, je pourrais faire

f_loc1 = f_loc2 = "/foo/bar"

Mais en elisp, j'écris

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

Je me demande s'il existe un moyen d'y parvenir en utilisant "/foo/bar"une seule fois?

ChillarAnand
la source
1
Salut, Chillar, pouvez-vous sélectionner la réponse de @tarsius comme réponse acceptée? Ma réponse démontre une solution qui vous donne ce que vous voulez, mais comme je l'ai dit, c'est "un exercice en quelque sorte" qui résout ce défi sans doute artificiel mais pas très utile dans la pratique. tarsuis a déployé beaucoup d'efforts pour démontrer cette considération évidente dans sa réponse, donc je pense que sa réponse mérite d'être "la bonne", donc tout le monde est à nouveau heureux.
Mark Karpov, le
1
Je dirais que la nécessité d'attribuer la même valeur à plusieurs variables indique un défaut de conception qui devrait être corrigé plutôt que de rechercher du sucre syntaxique pour couvrir :)
lunaryorn
1
@lunaryorn si vous voulez définir source& targetsur le même chemin au début et que je pourrais changer targetplus tard, comment les définir ensuite?
ChillarAnand
Afficher plus de code, afin que nous puissions dire ce que vous entendez par "variable" pour votre cas d'utilisation; c'est-à-dire quel type de variables vous essayez de définir. Voir mon commentaire pour la réponse de @ JordonBiondo.
Drew

Réponses:

21

setq renvoie la valeur, vous pouvez donc simplement:

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

Si vous ne voulez pas vous y fier, utilisez:

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

Personnellement, j'éviterais ce dernier et j'écrirais à la place:

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

ou même

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

Et la toute première approche que je n'utiliserais que dans des circonstances plutôt spéciales où elle met réellement l' accent sur l'intention, ou lorsque l'alternative serait d'utiliser la deuxième approche ou autre progn.


Vous pouvez arrêter de lire ici si ce qui précède vous rend heureux. Mais je pense que c'est une bonne occasion de vous avertir, vous et les autres, de ne pas abuser des macros.


Enfin, bien qu'il soit tentant d'écrire une macro comme setq-every"parce que c'est lisp et nous pouvons", je déconseille fortement de le faire. Vous gagnez très peu en le faisant:

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

Mais en même temps, il est beaucoup plus difficile pour les autres lecteurs de lire le code et d'être sûr de ce qui se passe. setq-everypourrait être implémenté de diverses manières et d'autres utilisateurs (y compris un futur que vous) ne peuvent pas savoir lequel choisit l'auteur, simplement en regardant le code qui l'utilise. [J'ai initialement fait quelques exemples ici, mais ceux-ci étaient considérés comme sarcastiques et une diatribe par quelqu'un.]

Vous pouvez gérer cette surcharge mentale à chaque fois que vous regardez setq-everyou vous pouvez simplement vivre avec un seul personnage supplémentaire. (Au moins dans le cas où vous ne devez définir que deux variables, mais je ne me souviens pas que j'ai jamais dû définir plus de deux variables à la même valeur à la fois. Si vous devez le faire, il y a probablement autre chose de mal avec votre code.)

D'un autre côté, si vous allez vraiment utiliser cette macro plus d'une demi-douzaine de fois, alors l'indirection supplémentaire en vaut la peine. Par exemple, j'utilise des macros comme celles --when-letde la dash.elbibliothèque. Cela rend plus difficile pour ceux qui le rencontrent dans mon code, mais surmonter cette difficulté de compréhension en vaut la peine car il est utilisé plus que très peu de fois et, du moins à mon avis, il rend le code plus lisible. .

On pourrait faire valoir que la même chose s'applique à setq-every, mais je pense qu'il est très peu probable que cette macro particulière soit utilisée plusieurs fois. Une bonne règle de base est que lorsque la définition de la macro (y compris la doc-string) ajoute plus de code que l'utilisation de la macro n'en supprime, alors la macro est très probablement exagérée (l'inverse n'est pas nécessairement vrai cependant).

tarse
la source
1
une fois que vous avez défini un var, setqvous pouvez référencer sa nouvelle valeur, vous pouvez donc également utiliser cette monstruosité: (setq a 10 b a c a d a e a)qui définit abcd et e sur 10.
Jordon Biondo
1
@JordonBiondo, oui, mais je pense que l'objectif d'OP est de réduire la répétition dans son code :-)
Mark Karpov
3
Je voudrais vous donner un +10 pour l'avertissement contre les abus de macro!
lunaryorn du
Je viens de créer une quête sur les meilleures pratiques macro. emacs.stackexchange.com/questions/21015 . J'espère que @tarsius et d'autres donneront d'excellentes réponses.
Yasushi Shoji
13

Une macro pour faire ce que vous voulez

Comme un exercice en quelque sorte:

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

Essayez-le maintenant:

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

Comment ça marche

Puisque les gens sont curieux de savoir comment cela fonctionne (selon les commentaires), voici une explication. Pour vraiment apprendre à écrire des macros, choisissez un bon livre Common Lisp (oui, Common Lisp, vous pourrez faire la même chose dans Emacs Lisp, mais Common Lisp est un peu plus puissant et a de meilleurs livres, à mon humble avis).

Les macros fonctionnent sur du code brut. Les macros n'évaluent pas leurs arguments (contrairement aux fonctions). Nous avons donc ici non évalué valueet collection de vars, qui pour notre macro ne sont que des symboles.

prognregroupe plusieurs setqformulaires en un seul. Cette chose:

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

Génère simplement une liste de setqformulaires, en utilisant l'exemple d'OP, ce sera:

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

Vous voyez, le formulaire est à l' intérieur du formulaire de guillemets et est préfixé par une virgule ,. À l'intérieur de la forme entre guillemets, tout est cité comme d'habitude, mais l' , évaluation «active» temporairement, donc tout mapcarest évalué au moment de la macroexpansion.

@Supprime enfin les parenthèses externes de la liste avec setqs, nous obtenons donc:

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

Les macros peuvent transformer arbitrairement votre code source, n'est-ce pas génial?

Une mise en garde

Voici une petite mise en garde, le premier argument sera évalué plusieurs fois, car cette macro se développe essentiellement comme suit:

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

Vous voyez, si vous avez une variable ou une chaîne ici, c'est OK, mais si vous écrivez quelque chose comme ceci:

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

Ensuite, votre fonction sera appelée plusieurs fois. Cela peut être indésirable. Voici comment le corriger à l'aide de once-only(disponible dans le package 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))))

Et le problème a disparu.

Mark Karpov
la source
1
Ce serait bien si vous pouviez ajouter quelques explications sur la façon dont cela fonctionne et ce que fait ce code plus en détail, donc la prochaine fois, les gens pourront écrire quelque chose comme ça :)
clemera
Vous ne voulez pas évaluer valueau moment de la macro-expansion.
tarsius
@tarsius, je n'ai pas: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Si elles valueétaient évaluées au moment de la macroexpansion, elles seraient remplacées par une chaîne réelle. Vous pouvez mettre un appel de fonction avec des effets secondaires, à la place de value, il ne sera pas évalué tant que le code développé ne sera pas exécuté, essayez-le. valuecar l'argument de la macro n'est pas évalué automatiquement. Le vrai problème est que valuecela sera évalué plusieurs fois, ce qui peut être indésirable, once-onlypeut aider ici.
Mark Karpov
1
Au lieu de mmt-once-only(qui nécessite un dep externe), vous pouvez utiliser macroexp-let2.
YoungFrog
2
Pouah. La question crie d'utiliser l'itération avec set, pas d'envelopper setqdans une macro. Ou utilisez simplement setqavec plusieurs arguments, y compris la répétition des arguments.
Drew
7

Puisque vous pouvez utiliser et manipuler des symboles dans lisp, vous pouvez simplement parcourir une liste de symboles et les utiliser 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
la source
2
Cela ne fonctionne pas pour les variables liées lexicalement
npostavs
@npostavs: Vrai, mais c'est peut-être ce que le PO veut / a besoin. S'il n'utilise pas de variables lexicales, c'est définitivement la voie à suivre. Cependant, vous avez raison, cette "variable" est ambiguë, donc en général la question pourrait signifier tout type de variable. Le cas d'utilisation réel n'est pas bien présenté, OMI.
Drew