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?

Drew
  • 75,699
  • 9
  • 109
  • 225
Chillar Anand
  • 4,042
  • 1
  • 23
  • 52
  • 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. – Mark Karpov Sep 08 '15 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 :) –  Sep 09 '15 at 06:19
  • 1
    @lunaryorn if want to set`source` & `target` to same path at the beginning and i might change `target` later, how to set them then? – Chillar Anand Sep 09 '15 at 08: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 '16 at 17:11

3 Answers3

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

Scott Weldon
  • 2,695
  • 1
  • 17
  • 31
tarsius
  • 25,298
  • 4
  • 69
  • 109
  • 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. – Jordon Biondo Sep 06 '15 at 11:57
  • 1
    @JordonBiondo, Yes, but I think aim of OP is to reduce repetition in his code :-) – Mark Karpov Sep 06 '15 at 12:22
  • 3
    I'd like to give you a +10 for the warning against macro abuse! –  Sep 08 '15 at 15:50
  • Just created a quest about macro best practice. http://emacs.stackexchange.com/questions/21015/. Hope @tarsius and others give a great answers. – Yasushi Shoji Mar 17 '16 at 04: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.

Mark Karpov
  • 4,893
  • 1
  • 24
  • 53
  • 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 06 '15 at 15:20
  • You don't want to evaluate `value` at macro-expansion time. – tarsius Sep 06 '15 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. – Mark Karpov Sep 06 '15 at 16:21
  • Meh. Yeah your right, were was my head. – tarsius Sep 06 '15 at 16:24
  • You do however evaluate `value` more than once. – tarsius Sep 06 '15 at 16:25
  • @hatschipuh, I've extended my answer to explain what's going on. – Mark Karpov Sep 06 '15 at 16:58
  • @Mark Thanks, this is instructive :) – clemera Sep 06 '15 at 18:38
  • 1
    Instead of `mmt-once-only` (which requires an external dep), you could use `macroexp-let2`. – YoungFrog Sep 07 '15 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 07 '15 at 23:14
  • @Drew, This answer shows one way to handle this, so end user can write more concise and elegant code. As I said in the first version of the answer "as an exercise of a sort". Normally I wound never use `set` and loop for this, if I had a literal value I would let bind it or created a variable/constant and then plainly repeated it in `setq` form. It's OK because you usually don't have more than 5 variables to set to the same value. – Mark Karpov Sep 08 '15 at 07:33
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))
Chillar Anand
  • 4,042
  • 1
  • 23
  • 52
Jordon Biondo
  • 12,332
  • 2
  • 41
  • 62
  • 2
    This doesn't work for lexically bound variables though – npostavs Sep 06 '15 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 '16 at 17:09