6

I often find myself wanting to do something like this:

(if-let* ((foo (get-foo))
          (_ (conditionp foo))
          (bar (get-bar-from-foo foo)))
    ...)

As an alternative to:

(let ((foo (get-foo)))
  (when (and foo (conditionp foo))
    (if-let ((bar (get-bar-from-foo)))
      ...)))

The problem is, the underscore is not used so it gives the bytecomp warning: Warning: variable ‘_’ not left unused.

I think the first example is much easier to read than the second, is there something similar to the first example which gets around this warning?

0

2 Answers 2

6

Since Emacs 26, the underscore (or any variable symbol in general) can be omitted:

if-let is a Lisp macro in `subr-x.el'.

(if-let SPEC THEN &rest ELSE)

  Probably introduced at or before Emacs version 25.1.

Bind variables according to SPEC and evaluate THEN or ELSE.
Evaluate each binding in turn, as in `let*', stopping if a
binding value is nil.  If all are non-nil return the value of
THEN, otherwise the last form in ELSE.

Each element of SPEC is a list (SYMBOL VALUEFORM) that binds
SYMBOL to the value of VALUEFORM.  An element can additionally be
of the form (VALUEFORM), which is evaluated and checked for nil;
i.e. SYMBOL can be omitted if only the test result is of             <======
interest.  It can also be of the form SYMBOL, then the binding of
SYMBOL is checked for nil.

So your example can be written as:

(eval-when-compile
  (require 'subr-x))

(if-let* ((foo (get-foo))
          ((conditionp foo))
          (bar (get-bar-from-foo foo)))
    ...)
1
3

Apologies for not helpfully answering the question posed, but hoping this might help anyway...

Instead of something "similar to the first", I'd likely use something that (I think) is simpler than either of them.

Your first example macro-"expands" to code that I think is clearer, than either of your examples:

(macroexpand '(if-let* ((foo (get-foo))
                        (_ (conditionp foo))
                        (bar (get-bar-from-foo foo)))
                  X))

gives:

(let* ((foo  (and t    (get-foo)))
       (_    (and foo  (conditionp foo)))
       (bar  (and _    (get-bar-from-foo foo))))
  (if bar X))

If I were to code that expansion by hand it would be simpler still:

(let* ((foo  (get-foo))
       (_    (and foo  (conditionp foo)))
       (bar  (and _    (get-bar-from-foo foo))))
  (if bar X))

And that assumes that the sexps would in reality be more complex. Otherwise I'd probably just use something like this:

(let ((foo (get-foo)))
  (and foo (conditionp foo) (get-bar-from-foo foo) X))

Even just a structural comparison of that with the if-let* form shown in @Basil's answer shows that the above sexp is simpler. Remove everything except the parens from each, to compare:

(((())(())(())))  ; Simplified `if-let*` form (@Basil's answer)
(((()))(()()))    ; Simple `let` shown above
3
  • > Assuming you meant to put another ) after (_ (conditionp foo) yep sorry (: I've accepted Basil's answer but I think I will end up doing one of the suggestions here for Emacs 25 compat, thanks May 19, 2021 at 15:11
  • I the two aren't equivalent if X evaluates bar anywhere. May 20, 2021 at 10:01
  • @TobySpeight: Yes, of course. Along with 8 million other possibilities. ;-)
    – Drew
    May 20, 2021 at 15:13

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.