Package: emacs;
Reported by: Mark Oteiza <mvoteiza <at> udel.edu>
Date: Sun, 27 Aug 2017 20:12:02 UTC
Severity: wishlist
Found in version 26.0.50
Done: Mark Oteiza <mvoteiza <at> udel.edu>
Bug is archived. No further changes may be made.
View this message in rfc822 format
From: Mark Oteiza <mvoteiza <at> udel.edu> To: Michael Heerdegen <michael_heerdegen <at> web.de> Cc: 28254 <at> debbugs.gnu.org, Noam Postavsky <npostavs <at> users.sourceforge.net> Subject: bug#28254: 26.0.50; SRFI-2 and-let* Date: Sun, 3 Sep 2017 18:39:59 -0400
On 03/09/17 at 07:48pm, Michael Heerdegen wrote: >Noam Postavsky <npostavs <at> users.sourceforge.net> writes: > >> I think I'd be okay with dropping support for the S = (S nil) thing in >> foo-let macros, so that all of the above would give (void-variable x). >> Although perhaps the incompatibility with plain let would be annoying? >> To be honest I hardly ever make use of S = (S nil) in plain let either >> so it wouldn't hit me at all. > >I think the main use case is to declare a local variable when you don't >care about the init value. In the case of if-let, S = (S nil) is not >useful, since you can't use that binding neither in the "then" clause >(because it won't be executed) nor in the "else" clauses (which ignore >all bindings). > >Even if an `if-let' form is the result of a macro expansion, the S = (S >nil) case isn't of any value. So I see no reasons to not drop support >for it. If I'm understanding correctly, it is being agreed that (let ((x 1)) (and-let* (x) x)) ;; => 1 because the macro expands to (let* ((x (and t x))) (if x x)) The following patch achieves this, though it breaks some existing subr-x tests which I haven't yet looked at carefully. diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 849ac19d6a..ec1990110a 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -83,10 +83,22 @@ thread-last `(internal--thread-argument nil ,@forms)) (defsubst internal--listify (elt) - "Wrap ELT in a list if it is not one." - (if (not (listp elt)) - (list elt) - elt)) + "Wrap ELT in a list if it is not one. +If ELT is of the form ((EXPR)), listify (EXPR) with a dummy symbol." + (message "%S" elt) + (cond + ;; could this be cleaner? + ((null elt) (list elt)) + ((symbolp elt) (list elt elt)) + ((nlistp elt) (list elt)) + ((and (null (cdr elt)) + (atom (car elt))) + (list (cl-gensym) (car elt))) + ((and (null (cdr elt)) + (let ((form (car elt))) + (or (listp form) (atom form)))) + (list (cl-gensym) (car elt))) + (t elt))) (defsubst internal--check-binding (binding) "Check BINDING is properly formed." @@ -98,7 +110,10 @@ internal--check-binding (defsubst internal--build-binding-value-form (binding prev-var) "Build the conditional value form for BINDING using PREV-VAR." - `(,(car binding) (and ,prev-var ,(cadr binding)))) + (let ((var (car binding))) + (if (and (null (cdr binding)) (atom (car binding)) (not (symbolp (car binding)))) + `(,var (and ,prev-var ,var)) + `(,var (and ,prev-var ,(cadr binding)))))) (defun internal--build-binding (binding prev-var) "Check and build a single BINDING with PREV-VAR." @@ -122,8 +137,11 @@ if-let* Each binding is evaluated in turn with `let*', and evaluation stops if a binding value is nil. If all are non-nil, the value of THEN is returned, or the last form in ELSE is returned. + Each element of VARLIST is a symbol (which is bound to nil) or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM). +An element can additionally be of the form (EXPR), which is +evaluated and checked for nil. In the special case you only want to bind a single value, VARLIST can just be a plain tuple. \n(fn VARLIST THEN ELSE...)" @@ -134,27 +152,43 @@ if-let* (not (listp (car bindings)))) ;; Adjust the single binding case (setq bindings (list bindings))) - `(let* ,(internal--build-bindings bindings) - (if ,(car (internal--listify (car (last bindings)))) - ,then - ,@else))) + (if bindings + `(let* ,(setq bindings (internal--build-bindings bindings)) + (if ,(caar (last bindings)) + ,then + ,@else)) + `(let* () ,then))) (defmacro when-let* (bindings &rest body) "Bind variables according to VARLIST and conditionally eval BODY. Each binding is evaluated in turn with `let*', and evaluation stops if a binding value is nil. If all are non-nil, the value of the last form in BODY is returned. + Each element of VARLIST is a symbol (which is bound to nil) or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM). +An element can additionally be of the form (EXPR), which is +evaluated and checked for nil. In the special case you only want to bind a single value, VARLIST can just be a plain tuple. \n(fn VARLIST BODY...)" - (declare (indent 1) (debug if-let)) + (declare (indent 1) (debug if-let*)) (list 'if-let bindings (macroexp-progn body))) (defalias 'if-let 'if-let*) (defalias 'when-let 'when-let*) -(defalias 'and-let* 'when-let*) + +(defmacro and-let* (varlist &rest body) + "Bind variables according to VARLIST and conditionally eval BODY. +Like `when-let*', except if BODY is empty and all the bindings +are non-nil, then the result is non-nil." + (declare (indent 1) (debug when-let*)) + ;; `(when-let* ,varlist ,@(or body '(t))) + (if varlist + `(let* ,(setq varlist (internal--build-bindings varlist)) + (if ,(caar (last varlist)) + ,@(or body `(,(caar (last varlist)))))) + `(let* () ,@(or body '(t))))) (defsubst hash-table-empty-p (hash-table) "Check whether HASH-TABLE is empty (has 0 elements)."
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.