14

Sometimes I need to set same value to multiple variables.

In Python, I could do

f_loc1 = f_loc2 = "/foo/bar"

But in elisp, I am writing

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

I am wondering if there a way to achieve this using "/foo/bar" only once?

4
  • 1
    Hi, Chillar, can you please select @tarsius' answer as the accepted one? My answer demonstrates solution that gives you what you want, but as I said it's "exercise of a sort" that solves this arguably artificial challenge but not very useful in practice. tarsuis has put much effort in demonstrating this obvious consideration in his answer, so I think his answer deserves to be "the correct one", so everyone is happy again. Sep 8, 2015 at 13:50
  • 1
    I'd argue that the need to assign the same value to multiple variables indicates a design flaw that should be fixed rather than looking for syntactic sugar to cover up :)
    – user227
    Sep 9, 2015 at 6:19
  • 1
    @lunaryorn if want to setsource & target to same path at the beginning and i might change target later, how to set them then? Sep 9, 2015 at 8:22
  • Show more code, so we can tell what you mean by "variable" for your use case; i.e., what kind of variables you are trying to set. See my comment for @JordonBiondo's answer.
    – Drew
    Apr 25, 2016 at 17:11

3 Answers 3

22

setq returns the value, so you can just:

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

Of if you don't want to rely on that, then use:

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

Personally I would avoid the latter and instead write:

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

or even

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

And the very first approach I would only use in rather special circumstances where it actually emphasis the intention, or when the alternative would be to use the second approach or else progn.


You can stop reading here if the above makes you happy. But I think this is a good opportunity to warn you and others not to abuse macros.


Finally, while it is tempting to write a macro like setq-every "because this is lisp and we can", I would strongly recommend against doing so. You gain very little by doing it:

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

But at the same time you make it much harder for other readers to read the code and be sure what happens. setq-every could be implemented in various ways and other users (including a future you) cannot know which one the author choose, just by looking at code that uses it. [I originally made some examples here but those were considered sarcastic and a rant by someone.]

You can deal with that mental overhead every time you look at setq-every or you can just live with a single extra character. (At least in the case where you have to set only two variables, but I cannot remember that I ever had to set more than two variables to the same value at once. If you have to do that, then there is probably something else wrong with your code.)

On the other hand if you are truly going to use this macro more than maybe half a dozen times, then the additional indirection might be worth it. For example I use macros like --when-let from the dash.el library. That does make it harder for those who first come across it in my code, but overcoming that difficulty in understanding is worth it because it is used more than just a very few times and, at least in my opinion, it makes the code more readable.

One could argue that the same applies to setq-every, but I think that it is very unlikely that this particular macro would be used more than a few times. A good rule of thumb is that when the definition of the macro (including the doc-string) adds more code than the use of the macro removes, then the macro very likely is overkill (the reverse isn't necessarily true though).

4
  • 1
    after you set one var in setq you can reference it's new value, so you could use this monstrosity too: (setq a 10 b a c a d a e a) which sets a b c d and e to 10. Sep 6, 2015 at 11:57
  • 1
    @JordonBiondo, Yes, but I think aim of OP is to reduce repetition in his code :-) Sep 6, 2015 at 12:22
  • 3
    I'd like to give you a +10 for the warning against macro abuse!
    – user227
    Sep 8, 2015 at 15:50
  • Just created a quest about macro best practice. emacs.stackexchange.com/questions/21015. Hope @tarsius and others give a great answers. Mar 17, 2016 at 4:39
13

A macro to do what you want

As an exercise of a sort:

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

Now try it:

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

How does it work

Since people are curious how it works (according to comments), here is an explanation. To really learn how to write macros pick a good Common Lisp book (yes, Common Lisp, you will be able to do the same stuff in Emacs Lisp, but Common Lisp is a bit more powerful and has better books, IMHO).

Macros operate on raw code. Macros don't evaluate their arguments (unlike functions). So we have here unevaluated value and collection of vars, which for our macro are just symbols.

progn groups several setq forms into one. This thing:

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

Just generates a list of setq forms, using OP's example it will be:

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

You see, the form is inside of backquote form and is prefixed with a comma ,. Inside backquoted form everything is quoted as usually, but , “turns-on” evaluation temporarily, so entire mapcar is evaluated at macroexpansion time.

Finally @ removes outer parenthesis from list with setqs, so we get:

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

Macros can arbitrary transform your source code, isn't it great?

A caveat

Here is a small caveat, first argument will be evaluated several times, because this macro essentially expands to the following:

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

You see, if you have a variable or string here it's OK, but if you write something like this:

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

Then your function will be called more than once. This may be undesirable. Here is how to fix it with help of once-only (available in MMT package):

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

And the problem is gone.

10
  • 1
    Would be nice if you can add some explanations how this works and what this code is doing in more detail, so next time people can write something like this themselves :)
    – clemera
    Sep 6, 2015 at 15:20
  • You don't want to evaluate value at macro-expansion time.
    – tarsius
    Sep 6, 2015 at 15:55
  • @tarsius, I don't: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2)) -> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). If value were evaluated at macroexpansion time it would be substituted with actual string. You can put function call with side effects, on place of value, it won't be evaluated until expanded code is run, try it. value as argument of macro is not automatically evaluated. Real issue is that value will be evaluated several times, which may be undesirable, once-only may help here. Sep 6, 2015 at 16:21
  • 1
    Instead of mmt-once-only (which requires an external dep), you could use macroexp-let2.
    – YoungFrog
    Sep 7, 2015 at 15:46
  • 2
    Ugh. The question cries out to use iteration with set, not to wrap setq in a macro. Or just use setq with multiple args, including repetition of args.
    – Drew
    Sep 7, 2015 at 23:14
7

Since you can use and manipulate symbols in lisp, you could simply loop over a list of symbols and use 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))
2
  • 2
    This doesn't work for lexically bound variables though
    – npostavs
    Sep 6, 2015 at 17:43
  • @npostavs: True, but it might be what the OP wants/needs. If s?he is not using lexical variables then it is definitely the way to go. You are right, though, that "variable" is ambiguous, so in general the question could mean any kind of variable. The actual use case is not well presented, IMO.
    – Drew
    Apr 25, 2016 at 17:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.