2

Q: how can I let-bind multiple variables conditional on x?

Assume that I have three variables, a, b, and c, that I want to let-bind conditional on some other x. How do I do that concisely and idiomatically?

Here's an example that works, but is repetitive and error-prone:

(let ((a (if t 1 4))
      (b (if t 2 5))
      (c (if t 3 6)))
  (list a b c))                         ; => (1 2 3)

Here's another example that works, but the use of setq feels both repetitive and inelegant:

(let (a b c)
  (if t
      (setq a 1
            b 2
            c 3)
    (setq a 4
          b 5
          c 6))
  (list a b c))                         ; => (1 2 3)

Here's an example that doesn't work, because the variables fall outside the scope of the let-binding:

(if t
    (let ((a 1)
          (b 2)
          (c 3)))
  (let ((a 4)
        (b 5)
        (c 6)))
  (list a b c))                         ; => nil

What is the idiomatic way to do this?

3 Answers 3

7

I think a reasonable way is to collect all the values into a list and then use a destructuring bind, e.g., pcase-let:

(pcase-let ((`(,a ,b ,c)
             (if t (list 1 2 3) (list 4 5 6))))
  (list a b c))

or cl-multiple-value-bind (a bit cleaner than the pcase version since it is specialised to lists, and only allows a single binding clause):

(cl-multiple-value-bind (a b c)
    (if t (list 1 2 3) (list 4 5 6))
  (list a b c))
2

There is no idiomatic way to do it, IMO. Do what you like.

If you want to be sure to test the condition only once that you are currently retesting, then here is another possibility, where I've used function my-test instead of your vague "x" or "X" (which doesn't seem to actually be referenced in the code you show, which uses t instead):

(let* ((test  (my-test))
       (a     (if test 1 4))
       (b     (if test 2 5))
       (c     (if test 3 6)))
  (list a b c))

Or if you care about the bindings of a, b, and c not being sequential:

(let ((test  (my-test)))
  (let ((a  (if test 1 4))
        (b  (if test 2 5))
        (c  (if test 3 6)))
     (list a b c))
0
0

As an alternative, consider storing your results in a list, and then access them using simple list operations such as car, cdr, or nth. This technique is showcased in the following code sample. While using cl-multiple-value-bind might enhance readability for some, the example below directly leverages fundamental list features.

(defun asciify-line-or-region ()
  "Transform the selected region or current line's content to ASCII."
  (interactive)
  (let ((begin-end
         (if (use-region-p)
             (cons (region-beginning) (region-end)) ; If region is selected, use its start and end.
           (cons (line-beginning-position) (line-end-position)))) ; Otherwise, use current line's start and end.
    (call-process-region (car begin-end) (cdr begin-end) "iconv" t t nil "--to-code=ASCII//TRANSLIT")))

This method is straightforward and taps into the core list manipulation capabilities of Emacs Lisp.

Your Answer

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

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