* `thunk-let'? @ 2017-10-08 20:12 Michael Heerdegen 2017-10-08 22:25 ` `thunk-let'? Michael Heerdegen ` (3 more replies) 0 siblings, 4 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-10-08 20:12 UTC (permalink / raw) To: Emacs Development; +Cc: Nicolas Petton, Stefan Monnier Hi, I often want thunks (like in "thunk.el") when I'm not sure if it's necessary to do some calculation (e.g. the result is used in an `if' branch) and want to save the time if not. So I bind a variable not to the expression to eval but to a thunk evaluating it, and use `thunk-force' everywhere I need to refer to the value. It would be cool if the programmer wouldn't need to speak that out, if you could hide away the details and use normal variables instead. You would have a kind of `let' that would look like making bindings to variables just like `let' but the expressions that are assigned are not evaluated before they are needed - maybe never, but also maximally once. I then realized that doing this should be trivial thanks to `cl-symbol-macrolet': instead of binding the original variables, you bind the expressions - wrapped inside `thunk-delay' - to helper vars. Then, you `symbol-macrolet' all original variables to a `thunk-force' of the according helper variable: #+begin_src emacs-lisp ;; -*- lexical-binding: t -*- (eval-when-compile (require 'cl-lib)) (defmacro thunk-let (bindings &rest body) "Like `let' but make delayed bindings. This is like `let' but all binding expressions are not calculated before they are used." (declare (indent 1)) (setq bindings (mapcar (pcase-lambda (`(,var ,binding)) (list (make-symbol (concat (symbol-name var) "_thunk")) var binding)) bindings)) `(let ,(mapcar (pcase-lambda (`(,thunk-var ,_var ,binding)) `(,thunk-var (thunk-delay ,binding))) bindings) (cl-symbol-macrolet ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) `(,var (thunk-force ,thunk-var))) bindings) ,@body))) (defmacro thunk-let* (bindings &rest body) "Like `let*' but make delayed bindings. This is like `let*' but all binding expressions are not calculated before they are used." (declare (indent 1)) (if (> (length bindings) 1) `(thunk-let (,(car bindings)) (thunk-let ,(cdr bindings) ,@body)) `(thunk-let ,bindings ,@body))) #+end_src An alternative name would be `delayed-let'. I think it would be very convenient. Here is a playground example to test when and if something is calculated (you need lexical-binding mode): #+begin_src emacs-lisp (defmacro calculate-with-message (varname expression) `(progn (message "Calculating %s..." ,varname) (sit-for 2) (prog1 ,expression (message "Calculating %s...done" ,varname) (sit-for 1)))) (thunk-let ((a (calculate-with-message "a" (+ 1 2))) (b (calculate-with-message "b" (* 10 3)))) (1+ b)) ;; etc. #+end_src What do people think about this idea? Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-10-08 20:12 `thunk-let'? Michael Heerdegen @ 2017-10-08 22:25 ` Michael Heerdegen 2017-10-09 3:10 ` `thunk-let'? Stefan Monnier ` (2 subsequent siblings) 3 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-10-08 22:25 UTC (permalink / raw) To: Emacs Development; +Cc: Nicolas Petton, Stefan Monnier Michael Heerdegen <michael_heerdegen@web.de> writes: > I then realized that doing this should be trivial thanks to > `cl-symbol-macrolet': instead of binding the original variables, you > bind the expressions - wrapped inside `thunk-delay' - to helper vars. > Then, you `symbol-macrolet' all original variables to a `thunk-force' of > the according helper variable: One limitation of that way of implementation would of course be that you can't set the bound variables in the BODY, e.g. this would not work: #+begin_src emacs-lisp (thunk-let ((a (+ 1 2))) (setq a (1+ a)) a) #+end_src Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-10-08 20:12 `thunk-let'? Michael Heerdegen 2017-10-08 22:25 ` `thunk-let'? Michael Heerdegen @ 2017-10-09 3:10 ` Stefan Monnier 2017-10-09 11:40 ` `thunk-let'? Michael Heerdegen 2017-10-09 8:00 ` `thunk-let'? Nicolas Petton 2017-12-08 20:38 ` A generalization of `thunk-let' (was: `thunk-let'?) Michael Heerdegen 3 siblings, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-10-09 3:10 UTC (permalink / raw) To: Michael Heerdegen; +Cc: Nicolas Petton, Emacs Development > An alternative name would be `delayed-let'. I think it would be very > convenient. Another name could be `lazy-let`. But if you add it to thunk.el, then thunk-let sounds like the better name. Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-10-09 3:10 ` `thunk-let'? Stefan Monnier @ 2017-10-09 11:40 ` Michael Heerdegen 2017-10-09 14:07 ` `thunk-let'? Michael Heerdegen 2017-10-09 15:38 ` [SUSPECTED SPAM] `thunk-let'? Stefan Monnier 0 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-10-09 11:40 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nicolas Petton, Emacs Development Stefan Monnier <monnier@iro.umontreal.ca> writes: > Another name could be `lazy-let`. But if you add it to thunk.el, then > thunk-let sounds like the better name. Ok, so let's be brave and aim to add it as `lazy-let' to subr-x. There is a question I want us to think about: what should the semantics of a `lazy-let' bound variable be if it is bound or set inside the BODY? I guess we can't inhibit that further references to the variable are also translated into calls of `thunk-force'. Which would mean we would have to make `thunk-force' setf'able (or introduce a new atrifical place form for that purpose). If I do this, the second question is whether any rebinds or sets of these variables should implicitly create lazy values again or not. In the first case, I would have to use a modified version of `cl-symbol-macrolet' to make that work, but I think it should be doable. The second case should be trivial, that's what we get with `cl-symbol-macrolet' out of the box. I wonder which behavior people would prefer. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-10-09 11:40 ` `thunk-let'? Michael Heerdegen @ 2017-10-09 14:07 ` Michael Heerdegen 2017-10-09 14:27 ` `thunk-let'? Michael Heerdegen 2017-10-09 15:38 ` [SUSPECTED SPAM] `thunk-let'? Stefan Monnier 1 sibling, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-10-09 14:07 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nicolas Petton, Emacs Development Michael Heerdegen <michael_heerdegen@web.de> writes: > The second case should be trivial, that's what we get with > `cl-symbol-macrolet' out of the box. I tried the first way: I implemented a place `lazy-let--thunk-value' that is only used internally, and went with `cl-symbol-macrolet'. Now doing the e.g. the following works (lexical-binding needed!): #+begin_src emacs-lisp (lazy-let ((a (+ 1 2)) (x (error "This evaluation would raise an error!")) (b (* 10 3)) (c (* 10 10 10))) (setq a (+ b a)) ; 33 (cl-incf b) ; 31 (+ a b c)) ==> 1064 #+end_src Here is the implementation: #+begin_src emacs-lisp ;; -*- lexical-binding: t -*- (eval-when-compile (require 'cl-lib)) (defun lazy-let--thunk-value (thunk) (funcall thunk)) (defun lazy-let--set-thunk-value (thunk value) (funcall thunk value)) (gv-define-simple-setter lazy-let--thunk-value lazy-let--set-thunk-value) (defmacro lazy-let--make-thunk (expr) (let ((forced (make-symbol "forced")) (val (make-symbol "val")) (args (make-symbol "args"))) `(let (,forced ,val) (lambda (&rest ,args) (if ,args (prog1 (setf ,val (car ,args)) (setq ,forced t)) (unless ,forced (setf ,val ,expr) (setf ,forced t)) ,val))))) (defmacro lazy-let (bindings &rest body) "Like `let' but make delayed bindings. This is like `let' but all binding expressions are not calculated before they are used." (declare (indent 1)) (setq bindings (mapcar (pcase-lambda (`(,var ,binding)) (list (make-symbol (concat (symbol-name var) "_thunk")) var binding)) bindings)) `(let ,(mapcar (pcase-lambda (`(,thunk-var ,_var ,binding)) `(,thunk-var (lazy-let--make-thunk ,binding))) bindings) (cl-symbol-macrolet ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) `(,var (lazy-let--thunk-value ,thunk-var))) bindings) ,@body))) (defmacro lazy-let* (bindings &rest body) "Like `let*' but make delayed bindings. This is like `let*' but all binding expressions are not calculated before they are used." (declare (indent 1)) (if (> (length bindings) 1) `(lazy-let (,(car bindings)) (lazy-let ,(cdr bindings) ,@body)) `(lazy-let ,bindings ,@body))) #+end_src Regards, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-10-09 14:07 ` `thunk-let'? Michael Heerdegen @ 2017-10-09 14:27 ` Michael Heerdegen 0 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-10-09 14:27 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nicolas Petton, Emacs Development Michael Heerdegen <michael_heerdegen@web.de> writes: > I tried the first way: I implemented a place `lazy-let--thunk-value' > that is only used internally, and went with `cl-symbol-macrolet'. BTW, with this kind of implementation, #+begin_src emacs-lisp (lazy-let* ((a 3) (b (- a 3))) (setq a 5) b) ==> 2 #+end_src I guess whether that result is surprising depends on your mental model. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* [SUSPECTED SPAM] Re: `thunk-let'? 2017-10-09 11:40 ` `thunk-let'? Michael Heerdegen 2017-10-09 14:07 ` `thunk-let'? Michael Heerdegen @ 2017-10-09 15:38 ` Stefan Monnier 2017-11-08 17:22 ` Michael Heerdegen 1 sibling, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-10-09 15:38 UTC (permalink / raw) To: Michael Heerdegen; +Cc: Nicolas Petton, Emacs Development >> Another name could be `lazy-let`. But if you add it to thunk.el, then >> thunk-let sounds like the better name. > Ok, so let's be brave and aim to add it as `lazy-let' to subr-x. > There is a question I want us to think about: what should the semantics > of a `lazy-let' bound variable be if it is bound or set inside the BODY? The semantics of `setq`ing such a var should be: compile-time error. The semantics of let-rebinding such a variable should be for the new binding to hide the outer (lazy) one. cl-symbol-macrolet currently doesn't provide this let-rebinding semantics, but it should (we already need to fix it for generator.el), so it's perfectly fine for the new code to just use cl-symbol-macrolet and then say that if rebinding isn't working right it's due to a (known) bug in cl-symbol-macrolet. Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-10-09 15:38 ` [SUSPECTED SPAM] `thunk-let'? Stefan Monnier @ 2017-11-08 17:22 ` Michael Heerdegen 2017-11-08 18:02 ` Stefan Monnier 2017-11-08 18:04 ` Eli Zaretskii 0 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-08 17:22 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nicolas Petton, Emacs Development [-- Attachment #1: Type: text/plain, Size: 513 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: > > Ok, so let's be brave and aim to add it as `lazy-let' to subr-x. > > There is a question I want us to think about: what should the semantics > > of a `lazy-let' bound variable be if it is bound or set inside the > > BODY? > > The semantics of `setq`ing such a var should be: compile-time error. > The semantics of let-rebinding such a variable should be for the new > binding to hide the outer (lazy) one. Is this patch acceptable (also attached as file)? [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Add-macros-lazy-let-and-lazy-let.patch --] [-- Type: text/x-diff, Size: 6489 bytes --] From 4941becd07f6ffbe387006248193d95b258be526 Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `lazy-let' and `lazy-let*' * lisp/emacs-lisp/subr-x.el (lazy-let, lazy-let*): New macros. * lisp/emacs-lisp/thunk.el (thunk-delay, thunk-force): Add autoload cookies. * test/lisp/emacs-lisp/subr-x-tests.el: Use lexical-binding. (subr-x-lazy-let-basic-test, subr-x-lazy-let*-basic-test) (subr-x-lazy-let-bound-vars-cant-be-bound-test) (subr-x-lazy-let-lazyness-test, subr-x-lazy-let*-lazyness-test) (subr-x-lazy-let-bad-binding-test): New tests for `lazy-let' and `lazy-let*. --- etc/NEWS | 4 +++ lisp/emacs-lisp/subr-x.el | 48 ++++++++++++++++++++++++++++++++++ lisp/emacs-lisp/thunk.el | 2 ++ test/lisp/emacs-lisp/subr-x-tests.el | 50 +++++++++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..8b1f659ebf 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -128,6 +128,10 @@ calling 'eldoc-message' directly. \f * Lisp Changes in Emacs 27.1 +--- +** The new macros 'lazy-let' and 'lazy-let*' are analogue to `let' and +`let*' but create bindings that are evaluated lazily. + --- ** The 'file-system-info' function is now available on all platforms. instead of just Microsoft platforms. This fixes a 'get-free-disk-space' diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 8ed29d8659..ce8956c96f 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -245,6 +245,54 @@ string-remove-suffix (substring string 0 (- (length string) (length suffix))) string)) +(defmacro lazy-let (bindings &rest body) + "Like `let' but make delayed bindings. + +This is like `let' but any binding expression is not evaluated +before the variable is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + ((or (and (pred symbolp) s) + `(,(and (pred symbolp) s))) + `(,s nil)) + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in lazy-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body))) + +(defmacro lazy-let* (bindings &rest body) + "Like `let*' but make delayed bindings. + +This is like `let*' but any binding expression is not evaluated +before the variable is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(lazy-let (,binding) ,expr)) + (nreverse bindings) + :initial-value `(progn ,@body))) + (provide 'subr-x) ;;; subr-x.el ends here diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el index 371d10444b..0c5d0b709e 100644 --- a/lisp/emacs-lisp/thunk.el +++ b/lisp/emacs-lisp/thunk.el @@ -46,6 +46,7 @@ (eval-when-compile (require 'cl-macs)) +;;;###autoload (defmacro thunk-delay (&rest body) "Delay the evaluation of BODY." (declare (debug t)) @@ -61,6 +62,7 @@ thunk-delay (setf ,forced t)) ,val))))) +;;;###autoload (defun thunk-force (delayed) "Force the evaluation of DELAYED. The result is cached and will be returned on subsequent calls diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 0e8871d9a9..c477a63a29 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -1,4 +1,4 @@ -;;; subr-x-tests.el --- Testing the extended lisp routines +;;; subr-x-tests.el --- Testing the extended lisp routines -*- lexical-binding: t -*- ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. @@ -538,6 +538,54 @@ (format "abs sum is: %s")) "abs sum is: 15"))) +\f +;; lazy-let tests + +(ert-deftest subr-x-lazy-let-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let*-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let-bound-vars-cant-be-bound-test () + "Test whether setting or binding a `lazy-let' bound variable fails." + (should-error (eval '(lazy-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t)) + (should-error (eval '(lazy-let ((x 1)) (let ((x 2)) x)) t))) + +(ert-deftest subr-x-lazy-let-lazyness-test () + "Test for lazyness." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (lazy-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest subr-x-lazy-let*-lazyness-test () + "Test lazyness of `lazy-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (lazy-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest subr-x-lazy-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (byte-compile (lazy-let ((x 1 1)) x))) + (should-error (byte-compile (lazy-let (27) x))) + (should-error (byte-compile (lazy-let x x)))) + (provide 'subr-x-tests) ;;; subr-x-tests.el ends here -- 2.14.2 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0001-Add-macros-lazy-let-and-lazy-let.patch --] [-- Type: text/x-diff, Size: 6489 bytes --] From 4941becd07f6ffbe387006248193d95b258be526 Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `lazy-let' and `lazy-let*' * lisp/emacs-lisp/subr-x.el (lazy-let, lazy-let*): New macros. * lisp/emacs-lisp/thunk.el (thunk-delay, thunk-force): Add autoload cookies. * test/lisp/emacs-lisp/subr-x-tests.el: Use lexical-binding. (subr-x-lazy-let-basic-test, subr-x-lazy-let*-basic-test) (subr-x-lazy-let-bound-vars-cant-be-bound-test) (subr-x-lazy-let-lazyness-test, subr-x-lazy-let*-lazyness-test) (subr-x-lazy-let-bad-binding-test): New tests for `lazy-let' and `lazy-let*. --- etc/NEWS | 4 +++ lisp/emacs-lisp/subr-x.el | 48 ++++++++++++++++++++++++++++++++++ lisp/emacs-lisp/thunk.el | 2 ++ test/lisp/emacs-lisp/subr-x-tests.el | 50 +++++++++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..8b1f659ebf 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -128,6 +128,10 @@ calling 'eldoc-message' directly. \f * Lisp Changes in Emacs 27.1 +--- +** The new macros 'lazy-let' and 'lazy-let*' are analogue to `let' and +`let*' but create bindings that are evaluated lazily. + --- ** The 'file-system-info' function is now available on all platforms. instead of just Microsoft platforms. This fixes a 'get-free-disk-space' diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 8ed29d8659..ce8956c96f 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -245,6 +245,54 @@ string-remove-suffix (substring string 0 (- (length string) (length suffix))) string)) +(defmacro lazy-let (bindings &rest body) + "Like `let' but make delayed bindings. + +This is like `let' but any binding expression is not evaluated +before the variable is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + ((or (and (pred symbolp) s) + `(,(and (pred symbolp) s))) + `(,s nil)) + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in lazy-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body))) + +(defmacro lazy-let* (bindings &rest body) + "Like `let*' but make delayed bindings. + +This is like `let*' but any binding expression is not evaluated +before the variable is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(lazy-let (,binding) ,expr)) + (nreverse bindings) + :initial-value `(progn ,@body))) + (provide 'subr-x) ;;; subr-x.el ends here diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el index 371d10444b..0c5d0b709e 100644 --- a/lisp/emacs-lisp/thunk.el +++ b/lisp/emacs-lisp/thunk.el @@ -46,6 +46,7 @@ (eval-when-compile (require 'cl-macs)) +;;;###autoload (defmacro thunk-delay (&rest body) "Delay the evaluation of BODY." (declare (debug t)) @@ -61,6 +62,7 @@ thunk-delay (setf ,forced t)) ,val))))) +;;;###autoload (defun thunk-force (delayed) "Force the evaluation of DELAYED. The result is cached and will be returned on subsequent calls diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 0e8871d9a9..c477a63a29 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -1,4 +1,4 @@ -;;; subr-x-tests.el --- Testing the extended lisp routines +;;; subr-x-tests.el --- Testing the extended lisp routines -*- lexical-binding: t -*- ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. @@ -538,6 +538,54 @@ (format "abs sum is: %s")) "abs sum is: 15"))) +\f +;; lazy-let tests + +(ert-deftest subr-x-lazy-let-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let*-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let-bound-vars-cant-be-bound-test () + "Test whether setting or binding a `lazy-let' bound variable fails." + (should-error (eval '(lazy-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t)) + (should-error (eval '(lazy-let ((x 1)) (let ((x 2)) x)) t))) + +(ert-deftest subr-x-lazy-let-lazyness-test () + "Test for lazyness." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (lazy-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest subr-x-lazy-let*-lazyness-test () + "Test lazyness of `lazy-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (lazy-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest subr-x-lazy-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (byte-compile (lazy-let ((x 1 1)) x))) + (should-error (byte-compile (lazy-let (27) x))) + (should-error (byte-compile (lazy-let x x)))) + (provide 'subr-x-tests) ;;; subr-x-tests.el ends here -- 2.14.2 [-- Attachment #4: Type: text/plain, Size: 20 bytes --] Thanks, Michael. ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-11-08 17:22 ` Michael Heerdegen @ 2017-11-08 18:02 ` Stefan Monnier 2017-11-09 15:14 ` Michael Heerdegen 2017-11-08 18:04 ` Eli Zaretskii 1 sibling, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-11-08 18:02 UTC (permalink / raw) To: Michael Heerdegen; +Cc: Nicolas Petton, Emacs Development > + (cl-callf2 mapcar > + (lambda (binding) > + (pcase binding > + ((or (and (pred symbolp) s) > + `(,(and (pred symbolp) s))) > + `(,s nil)) > + (`(,(pred symbolp) ,_) binding) > + (_ (signal 'error (cons "Bad binding in lazy-let" > + (list binding)))))) > + bindings) I think lazily binding a variable to nil is useless, so I'd drop the first pcase branch above. > + :initial-value `(progn ,@body))) You can use `macroexp-progn` to avoid constructing a `progn` in the common case where body is already a single expression. > +;;;###autoload > (defmacro thunk-delay (&rest body) Maybe a better option is to add a `require` in the expansion of lazy-let? Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-11-08 18:02 ` Stefan Monnier @ 2017-11-09 15:14 ` Michael Heerdegen 2017-11-09 18:39 ` `thunk-let'? Michael Heerdegen 2017-11-10 10:01 ` [SUSPECTED SPAM] `thunk-let'? Eli Zaretskii 0 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 15:14 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nicolas Petton, Emacs Development [-- Attachment #1: Type: text/plain, Size: 690 bytes --] Stefan Monnier <monnier@IRO.UMontreal.CA> writes: > I think lazily binding a variable to nil is useless, so I'd drop the > first pcase branch above. Makes sense -- done. > > + :initial-value `(progn ,@body))) > > You can use `macroexp-progn` to avoid constructing a `progn` in the > common case where body is already a single expression. Done (though the byte compiler already dismissed this redundant `progn' in every case, so I didn't care about it). > > +;;;###autoload > > (defmacro thunk-delay (&rest body) > > Maybe a better option is to add a `require` in the expansion of lazy-let? That should be fine, so I've done it as well. This is the updated (cumulative) patch: [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Add-macros-lazy-let-and-lazy-let.patch --] [-- Type: text/x-diff, Size: 5842 bytes --] From f8bb4d2872c366af45032a3d8d1d2e907c3e4d9d Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `lazy-let' and `lazy-let*' * lisp/emacs-lisp/subr-x.el (lazy-let, lazy-let*): New macros. * test/lisp/emacs-lisp/subr-x-tests.el: Use lexical-binding. (subr-x-lazy-let-basic-test, subr-x-lazy-let*-basic-test) (subr-x-lazy-let-bound-vars-cant-be-bound-test) (subr-x-lazy-let-lazyness-test, subr-x-lazy-let*-lazyness-test) (subr-x-lazy-let-bad-binding-test): New tests for `lazy-let' and `lazy-let*. --- etc/NEWS | 3 +++ lisp/emacs-lisp/subr-x.el | 49 +++++++++++++++++++++++++++++++++++ test/lisp/emacs-lisp/subr-x-tests.el | 50 +++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..451688c665 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -128,6 +128,9 @@ calling 'eldoc-message' directly. \f * Lisp Changes in Emacs 27.1 +** The new macros 'lazy-let' and 'lazy-let*' are analogue to `let' and +`let*' but create bindings that are evaluated lazily. + --- ** The 'file-system-info' function is now available on all platforms. instead of just Microsoft platforms. This fixes a 'get-free-disk-space' diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 8ed29d8659..8e64cbd4ce 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -245,6 +245,55 @@ string-remove-suffix (substring string 0 (- (length string) (length suffix))) string)) +(defmacro lazy-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in lazy-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(progn + (require 'thunk) + (let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body)))) + +(defmacro lazy-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(lazy-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + (provide 'subr-x) ;;; subr-x.el ends here diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 0e8871d9a9..c477a63a29 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -1,4 +1,4 @@ -;;; subr-x-tests.el --- Testing the extended lisp routines +;;; subr-x-tests.el --- Testing the extended lisp routines -*- lexical-binding: t -*- ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. @@ -538,6 +538,54 @@ (format "abs sum is: %s")) "abs sum is: 15"))) +\f +;; lazy-let tests + +(ert-deftest subr-x-lazy-let-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let*-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let-bound-vars-cant-be-bound-test () + "Test whether setting or binding a `lazy-let' bound variable fails." + (should-error (eval '(lazy-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t)) + (should-error (eval '(lazy-let ((x 1)) (let ((x 2)) x)) t))) + +(ert-deftest subr-x-lazy-let-lazyness-test () + "Test for lazyness." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (lazy-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest subr-x-lazy-let*-lazyness-test () + "Test lazyness of `lazy-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (lazy-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest subr-x-lazy-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (byte-compile (lazy-let ((x 1 1)) x))) + (should-error (byte-compile (lazy-let (27) x))) + (should-error (byte-compile (lazy-let x x)))) + (provide 'subr-x-tests) ;;; subr-x-tests.el ends here -- 2.14.2 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0001-Add-macros-lazy-let-and-lazy-let.patch --] [-- Type: text/x-diff, Size: 5842 bytes --] From f8bb4d2872c366af45032a3d8d1d2e907c3e4d9d Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `lazy-let' and `lazy-let*' * lisp/emacs-lisp/subr-x.el (lazy-let, lazy-let*): New macros. * test/lisp/emacs-lisp/subr-x-tests.el: Use lexical-binding. (subr-x-lazy-let-basic-test, subr-x-lazy-let*-basic-test) (subr-x-lazy-let-bound-vars-cant-be-bound-test) (subr-x-lazy-let-lazyness-test, subr-x-lazy-let*-lazyness-test) (subr-x-lazy-let-bad-binding-test): New tests for `lazy-let' and `lazy-let*. --- etc/NEWS | 3 +++ lisp/emacs-lisp/subr-x.el | 49 +++++++++++++++++++++++++++++++++++ test/lisp/emacs-lisp/subr-x-tests.el | 50 +++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..451688c665 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -128,6 +128,9 @@ calling 'eldoc-message' directly. \f * Lisp Changes in Emacs 27.1 +** The new macros 'lazy-let' and 'lazy-let*' are analogue to `let' and +`let*' but create bindings that are evaluated lazily. + --- ** The 'file-system-info' function is now available on all platforms. instead of just Microsoft platforms. This fixes a 'get-free-disk-space' diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 8ed29d8659..8e64cbd4ce 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -245,6 +245,55 @@ string-remove-suffix (substring string 0 (- (length string) (length suffix))) string)) +(defmacro lazy-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in lazy-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(progn + (require 'thunk) + (let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body)))) + +(defmacro lazy-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(lazy-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + (provide 'subr-x) ;;; subr-x.el ends here diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 0e8871d9a9..c477a63a29 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -1,4 +1,4 @@ -;;; subr-x-tests.el --- Testing the extended lisp routines +;;; subr-x-tests.el --- Testing the extended lisp routines -*- lexical-binding: t -*- ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. @@ -538,6 +538,54 @@ (format "abs sum is: %s")) "abs sum is: 15"))) +\f +;; lazy-let tests + +(ert-deftest subr-x-lazy-let-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let*-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let-bound-vars-cant-be-bound-test () + "Test whether setting or binding a `lazy-let' bound variable fails." + (should-error (eval '(lazy-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t)) + (should-error (eval '(lazy-let ((x 1)) (let ((x 2)) x)) t))) + +(ert-deftest subr-x-lazy-let-lazyness-test () + "Test for lazyness." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (lazy-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest subr-x-lazy-let*-lazyness-test () + "Test lazyness of `lazy-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (lazy-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest subr-x-lazy-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (byte-compile (lazy-let ((x 1 1)) x))) + (should-error (byte-compile (lazy-let (27) x))) + (should-error (byte-compile (lazy-let x x)))) + (provide 'subr-x-tests) ;;; subr-x-tests.el ends here -- 2.14.2 [-- Attachment #4: Type: text/plain, Size: 20 bytes --] Thanks, Michael. ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 15:14 ` Michael Heerdegen @ 2017-11-09 18:39 ` Michael Heerdegen 2017-11-09 18:48 ` `thunk-let'? Stefan Monnier 2017-11-10 10:01 ` [SUSPECTED SPAM] `thunk-let'? Eli Zaretskii 1 sibling, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 18:39 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nicolas Petton, Emacs Development [-- Attachment #1: Type: text/plain, Size: 333 bytes --] Michael Heerdegen <michael_heerdegen@web.de> writes: > > Maybe a better option is to add a `require` in the expansion of > > lazy-let? But then we need to wrap it in `eval-and-compile' because we want to expand the (macro) `thunk-delay' that appears in the expansion at compile time. Attached is the cumulative patch doing that. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Add-macros-lazy-let-and-lazy-let.patch --] [-- Type: text/x-diff, Size: 5861 bytes --] From ca7f5a62e2bda9ee5e8d9199ae80714674e18511 Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `lazy-let' and `lazy-let*' * lisp/emacs-lisp/subr-x.el (lazy-let, lazy-let*): New macros. * test/lisp/emacs-lisp/subr-x-tests.el: Use lexical-binding. (subr-x-lazy-let-basic-test, subr-x-lazy-let*-basic-test) (subr-x-lazy-let-bound-vars-cant-be-bound-test) (subr-x-lazy-let-lazyness-test, subr-x-lazy-let*-lazyness-test) (subr-x-lazy-let-bad-binding-test): New tests for `lazy-let' and `lazy-let*. --- etc/NEWS | 3 +++ lisp/emacs-lisp/subr-x.el | 49 +++++++++++++++++++++++++++++++++++ test/lisp/emacs-lisp/subr-x-tests.el | 50 +++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..451688c665 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -128,6 +128,9 @@ calling 'eldoc-message' directly. \f * Lisp Changes in Emacs 27.1 +** The new macros 'lazy-let' and 'lazy-let*' are analogue to `let' and +`let*' but create bindings that are evaluated lazily. + --- ** The 'file-system-info' function is now available on all platforms. instead of just Microsoft platforms. This fixes a 'get-free-disk-space' diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 8ed29d8659..ea22bee13f 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -245,6 +245,55 @@ string-remove-suffix (substring string 0 (- (length string) (length suffix))) string)) +(defmacro lazy-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in lazy-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(progn + (eval-and-compile (require 'thunk)) + (let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body)))) + +(defmacro lazy-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time. + +It is not allowed to set `lazy-let' or `lazy-let*' bound +variables." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(lazy-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + (provide 'subr-x) ;;; subr-x.el ends here diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 0e8871d9a9..c477a63a29 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -1,4 +1,4 @@ -;;; subr-x-tests.el --- Testing the extended lisp routines +;;; subr-x-tests.el --- Testing the extended lisp routines -*- lexical-binding: t -*- ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. @@ -538,6 +538,54 @@ (format "abs sum is: %s")) "abs sum is: 15"))) +\f +;; lazy-let tests + +(ert-deftest subr-x-lazy-let-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let*-basic-test () + "Test whether bindings are established." + (should (equal (lazy-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest subr-x-lazy-let-bound-vars-cant-be-bound-test () + "Test whether setting or binding a `lazy-let' bound variable fails." + (should-error (eval '(lazy-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t)) + (should-error (eval '(lazy-let ((x 1)) (let ((x 2)) x)) t))) + +(ert-deftest subr-x-lazy-let-lazyness-test () + "Test for lazyness." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (lazy-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest subr-x-lazy-let*-lazyness-test () + "Test lazyness of `lazy-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (lazy-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest subr-x-lazy-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (byte-compile (lazy-let ((x 1 1)) x))) + (should-error (byte-compile (lazy-let (27) x))) + (should-error (byte-compile (lazy-let x x)))) + (provide 'subr-x-tests) ;;; subr-x-tests.el ends here -- 2.14.2 [-- Attachment #3: Type: text/plain, Size: 20 bytes --] Thanks, Michael. ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 18:39 ` `thunk-let'? Michael Heerdegen @ 2017-11-09 18:48 ` Stefan Monnier 2017-11-22 2:50 ` `thunk-let'? Michael Heerdegen 0 siblings, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-11-09 18:48 UTC (permalink / raw) To: Michael Heerdegen; +Cc: Nicolas Petton, Emacs Development >> > Maybe a better option is to add a `require` in the expansion of >> > lazy-let? > But then we need to wrap it in `eval-and-compile' because we want to > expand the (macro) `thunk-delay' that appears in the expansion at > compile time. Yeah, pretty ugly. Now I see why my intuition told me that lazy-let should be in thunk.el ;-) Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 18:48 ` `thunk-let'? Stefan Monnier @ 2017-11-22 2:50 ` Michael Heerdegen 2017-11-22 3:43 ` `thunk-let'? Eli Zaretskii 2017-11-22 17:44 ` `thunk-let'? Gemini Lasswell 0 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-22 2:50 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nicolas Petton, Eli Zaretskii, Emacs Development [-- Attachment #1: Type: text/plain, Size: 390 bytes --] Stefan Monnier <monnier@IRO.UMontreal.CA> writes: > Yeah, pretty ugly. Now I see why my intuition told me that lazy-let > should be in thunk.el ;-) Ok, I updated the patch accordingly (attached): the stuff goes to thunk.el now, I added a note to the file header, renamed everything, added some more details to the docstrings, moved the tests, and removed the "---" from the News entry. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Add-macros-thunk-let-and-thunk-let.patch --] [-- Type: text/x-diff, Size: 5848 bytes --] From 250b999e094ccdf11e37bb80f2d6a41b64b96dfd Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `thunk-let' and `thunk-let*' * lisp/emacs-lisp/thunk.el (thunk-let, thunk-let*): New macros. * test/lisp/emacs-lisp/thunk-tests.el: (thunk-let-basic-test, thunk-let*-basic-test) (thunk-let-bound-vars-cant-be-set-test) (thunk-let-laziness-test, thunk-let*-laziness-test) (thunk-let-bad-binding-test): New tests for `thunk-let' and `thunk-let*. --- etc/NEWS | 4 +++ lisp/emacs-lisp/thunk.el | 56 +++++++++++++++++++++++++++++++++++++ test/lisp/emacs-lisp/thunk-tests.el | 50 +++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..bfa8be39f9 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -109,6 +109,10 @@ Snake and Pong are more playable on HiDPI displays. *** Completing filenames in the minibuffer via 'C-TAB' now uses the styles as configured by the variable 'completion-styles'. +** Thunk +*** The new macros 'thunk-let' and 'thunk-let*' are analogue to `let' +and `let*' but create bindings that are evaluated lazily. + \f * New Modes and Packages in Emacs 27.1 diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el index 371d10444b..9cb16857f4 100644 --- a/lisp/emacs-lisp/thunk.el +++ b/lisp/emacs-lisp/thunk.el @@ -41,6 +41,10 @@ ;; following: ;; ;; (thunk-force delayed) +;; +;; This file also defines macros `thunk-let' and `thunk-let*' that are +;; analogous to `let' and `let*' but provide lazy evaluation of +;; bindings by using thunks implicitly (i.e. in the expansion). ;;; Code: @@ -71,5 +75,57 @@ thunk-evaluated-p "Return non-nil if DELAYED has been evaluated." (funcall delayed t)) +(defmacro thunk-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in thunk-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body))) + +(defmacro thunk-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(thunk-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + + (provide 'thunk) ;;; thunk.el ends here diff --git a/test/lisp/emacs-lisp/thunk-tests.el b/test/lisp/emacs-lisp/thunk-tests.el index 973a14b818..e122098b16 100644 --- a/test/lisp/emacs-lisp/thunk-tests.el +++ b/test/lisp/emacs-lisp/thunk-tests.el @@ -51,5 +51,55 @@ (thunk-force thunk) (should (= x 1)))) + +\f +;; thunk-let tests + +(ert-deftest thunk-let-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest thunk-let*-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest thunk-let-bound-vars-cant-be-set-test () + "Test whether setting a `thunk-let' bound variable fails." + (should-error + (eval '(thunk-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t))) + +(ert-deftest thunk-let-laziness-test () + "Test laziness of `thunk-let'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (thunk-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest thunk-let*-laziness-test () + "Test laziness of `thunk-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (thunk-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest thunk-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (byte-compile (thunk-let ((x 1 1)) x))) + (should-error (byte-compile (thunk-let (27) x))) + (should-error (byte-compile (thunk-let x x)))) + + (provide 'thunk-tests) ;;; thunk-tests.el ends here -- 2.15.0 [-- Attachment #3: Type: text/plain, Size: 238 bytes --] Is this ok to install now? @Eli: I think we can still add the words I wrote to the manual. What is the cheapest way to learn enough of texinfo and its usage by the Emacs manual to be able to create a correct patch? Thanks, Michael. ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 2:50 ` `thunk-let'? Michael Heerdegen @ 2017-11-22 3:43 ` Eli Zaretskii 2017-11-22 16:16 ` `thunk-let'? Eli Zaretskii 2017-11-22 17:44 ` `thunk-let'? Gemini Lasswell 1 sibling, 1 reply; 77+ messages in thread From: Eli Zaretskii @ 2017-11-22 3:43 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: Nicolas Petton <nicolas@petton.fr>, Emacs Development <emacs-devel@gnu.org>, > Eli Zaretskii <eliz@gnu.org> > Date: Wed, 22 Nov 2017 03:50:47 +0100 > > Ok, I updated the patch accordingly (attached): the stuff goes to > thunk.el now, I added a note to the file header, renamed everything, > added some more details to the docstrings, moved the tests, and removed > the "---" from the News entry. Thanks. > Is this ok to install now? Yes, once we have the patch for manual. > @Eli: I think we can still add the words I wrote to the manual. What is > the cheapest way to learn enough of texinfo and its usage by the Emacs > manual to be able to create a correct patch? I will send a message with "Texinfo 101" later today. Thanks. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 3:43 ` `thunk-let'? Eli Zaretskii @ 2017-11-22 16:16 ` Eli Zaretskii 2017-11-22 19:25 ` `thunk-let'? Michael Heerdegen 2017-11-23 2:59 ` `thunk-let'? Michael Heerdegen 0 siblings, 2 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-22 16:16 UTC (permalink / raw) To: michael_heerdegen, nicolas, monnier; +Cc: emacs-devel > Date: Wed, 22 Nov 2017 05:43:30 +0200 > From: Eli Zaretskii <eliz@gnu.org> > Cc: nicolas@petton.fr, monnier@IRO.UMontreal.CA, emacs-devel@gnu.org > > > @Eli: I think we can still add the words I wrote to the manual. What is > > the cheapest way to learn enough of texinfo and its usage by the Emacs > > manual to be able to create a correct patch? > > I will send a message with "Texinfo 101" later today. Here it is. Let me know if something in your plain-text draft is not covered. 1. General markup: Symbols like `this' should be written like @code{this}. Formal arguments and other meta-syntactic variables like THIS should be written like @var{this}. Keyboard input by user should be written @kbd{like this}. Keys mentioned by their name should be written as @key{RET}. First time you mention some important term, write @dfn{term}. File names should be written as @file{like/this}. Program names should be written as @command{prog}. Emphasized text is written @emph{like this}. 2. Functions, macros, variables, etc. Function: @defun my-func arg1 arg2 &optional arg3 Put description here, referencing arguments as @var{arg1} etc. @end defun Likewise with macros, but use @defmac. Likewise with special forms, like condition-case, but use @defspec. For commands, use the generalized @deffn: @deffn Command foo arg1 ... @end deffn For variables, use @defvar, for user options @defopt. 3. Examples: Like this: @example (defun f (number) (lazy-let ((derived-number (progn (message "Calculating 1 plus 2 times %d" number) (1+ (* 2 number))))) (if (> number 10) derived-number number))) @end example 4. Useful glyphs: Result of evaluation: @result{} 42 Expansion of a macro: @expansion{} 42 Generation of output: @print{} 42 Error message: @error{} Wrong type argument: stringp, nil Show point: This is the @point{}buffer text 5. Cross-references: As a separate sentence: @xref{Node name}, for the details. In the middle of a sentence ... see @ref{Node name}, for more. In parentheses: Some text (@pxref{Some node}) more text. 6. Indexing: It is a good idea to have an index entry before a chunk of text describing some topic. For example, in your case, I would have these index entries before the beginning of text: @cindex deferred evaluation @cindex lazy evaluation Functions, variables, etc. described using the @def* commands are automatically added to the index, so you don't need to index them separately. 7. Where to put this stuff: This should be a separate section under the Evaluation chapter, as the last section, after "Eval". (You should also add it to the menu in "Evaluation" and to the top-level detailed menu in elisp.texi.) You begin a section like this: @node Deferred and Lazy Evaluation @section Deferred and Lazy Evaluation These 2 lines start a new node. Follow them with the index entries mentioned above. 8. Always run "make" after modifying the manual, to make sure you didn't leave any errors in the text! 9. Last, but not least: consult the Texinfo manual when in doubt. It is well indexed, so that every command can be easily found by typing "i COMMAND", which invokes Index-search. Example: "i xref RET". ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 16:16 ` `thunk-let'? Eli Zaretskii @ 2017-11-22 19:25 ` Michael Heerdegen 2017-11-22 20:00 ` `thunk-let'? Eli Zaretskii 2017-11-23 2:59 ` `thunk-let'? Michael Heerdegen 1 sibling, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-22 19:25 UTC (permalink / raw) To: Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > > I will send a message with "Texinfo 101" later today. > > Here it is. Let me know if something in your plain-text draft is not > covered. Great, that will help a lot. But where should the new page go? Under "Control Structures" maybe? It seems a bit misplaced everywhere, also because it's a separate library with no autoloads (I think we didn't want autoloads), so you must explicitly `require' it, unlike all other stuff there. Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 19:25 ` `thunk-let'? Michael Heerdegen @ 2017-11-22 20:00 ` Eli Zaretskii 0 siblings, 0 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-22 20:00 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: nicolas@petton.fr, monnier@IRO.UMontreal.CA, emacs-devel@gnu.org > Date: Wed, 22 Nov 2017 20:25:09 +0100 > > But where should the new page go? Under "Control Structures" maybe? See "Where to put this stuff". ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 16:16 ` `thunk-let'? Eli Zaretskii 2017-11-22 19:25 ` `thunk-let'? Michael Heerdegen @ 2017-11-23 2:59 ` Michael Heerdegen 2017-11-23 4:15 ` `thunk-let'? Michael Heerdegen 2017-11-23 16:04 ` `thunk-let'? Eli Zaretskii 1 sibling, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-23 2:59 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > Here it is. Let me know if something in your plain-text draft is not > covered. This helped a lot. My first version even compiled without error. I'll post the updated patch soon. > Program names should be written as @command{prog}. If you send this little introduction to other people: it was not clear to me what kind of "program" this refers to. Maybe replace the example with something like @command{gzip}. The only question that I wondered about was the significance of whitespace, but it was easy to find out that it's collapsed. And it also doesn't matter whether I indent the first line of a new paragraph in the texi source or not, right? Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-23 2:59 ` `thunk-let'? Michael Heerdegen @ 2017-11-23 4:15 ` Michael Heerdegen 2017-11-23 16:34 ` `thunk-let'? Pip Cet 2017-11-24 8:36 ` `thunk-let'? Eli Zaretskii 2017-11-23 16:04 ` `thunk-let'? Eli Zaretskii 1 sibling, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-23 4:15 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 334 bytes --] Michael Heerdegen <michael_heerdegen@web.de> writes: > This helped a lot. My first version even compiled without error. I'll > post the updated patch soon. Ok, here is what I have, with everything discusses included (see also attached). I will proofread it once more, maybe others will want to have a look in the meantime, too. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Add-macros-thunk-let-and-thunk-let.patch --] [-- Type: text/x-diff, Size: 11083 bytes --] From 6d7028defb30b403505a72ecf68ac52fe478d562 Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `thunk-let' and `thunk-let*' * lisp/emacs-lisp/thunk.el (thunk-let, thunk-let*): New macros. * test/lisp/emacs-lisp/thunk-tests.el: (thunk-let-basic-test, thunk-let*-basic-test) (thunk-let-bound-vars-cant-be-set-test) (thunk-let-laziness-test, thunk-let*-laziness-test) (thunk-let-bad-binding-test): New tests for `thunk-let' and `thunk-let*. * doc/lispref/eval.texi (Deferred Eval): New section. * doc/lispref/elisp.texi: Update menu. --- doc/lispref/elisp.texi | 1 + doc/lispref/eval.texi | 118 ++++++++++++++++++++++++++++++++++-- etc/NEWS | 4 ++ lisp/emacs-lisp/thunk.el | 56 +++++++++++++++++ test/lisp/emacs-lisp/thunk-tests.el | 50 +++++++++++++++ 5 files changed, 224 insertions(+), 5 deletions(-) diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index c752594584..a271749e04 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -455,6 +455,7 @@ Top the program). * Backquote:: Easier construction of list structure. * Eval:: How to invoke the Lisp interpreter explicitly. +* Deferred Eval:: Deferred and lazy evaluation of forms. Kinds of Forms diff --git a/doc/lispref/eval.texi b/doc/lispref/eval.texi index 064fca22ff..8b61e39142 100644 --- a/doc/lispref/eval.texi +++ b/doc/lispref/eval.texi @@ -20,11 +20,12 @@ Evaluation @ifnottex @menu -* Intro Eval:: Evaluation in the scheme of things. -* Forms:: How various sorts of objects are evaluated. -* Quoting:: Avoiding evaluation (to put constants in the program). -* Backquote:: Easier construction of list structure. -* Eval:: How to invoke the Lisp interpreter explicitly. +* Intro Eval:: Evaluation in the scheme of things. +* Forms:: How various sorts of objects are evaluated. +* Quoting:: Avoiding evaluation (to put constants in the program). +* Backquote:: Easier construction of list structure. +* Eval:: How to invoke the Lisp interpreter explicitly. +* Deferred Eval:: Deferred and lazy evaluation of forms @end menu @node Intro Eval @@ -877,3 +878,110 @@ Eval @end group @end example @end defvar + +@node Deferred Eval +@section Deferred and Lazy Evaluation + +@cindex deferred evaluation +@cindex lazy evaluation + + + Sometimes it is useful to delay the evaluation of an expression, for +example if you want to avoid to perform a time-consuming calculation +in the case that it turns out that the result is not needed in the +future of the program. + + +@defmac thunk-delay forms... +Return a thunk for evaluating the @var{forms}. A thunk is a closure +that evaluates the @var{forms} in the lexical environment present when +@code{thunk-delay} had been called. Using this macro requires +@code{lexical-binding}. +@end defmac + +@defun thunk-force thunk +Force @var{thunk} to perform the evaluation of the forms specified to the +@code{thunk-delay} that created the thunk. The result of the evaluation +of the last form is returned. The @var{thunk} also "remembers" that it has +been forced: Any further calls of @code{thunk-force} on the same @var{thunk} +will just return the same result without evaluating the @var{forms} again. +@end defun + +@defmac lazy-let (bindings...) forms... +This macro is analogous to @code{let} but creates "lazy" variable +bindings. Any binding has the form (@var{symbol} @var{value-form}). +Unlike @code{let}, the evaluation of any @var{value-form} is deferred +until the binding of the according @var{symbol} is used for the first +time when evaluating the @var{forms}. Any @var{value-form} is evaluated +at most once. Using this macro requires @code{lexical-binding}. +@end defmac + +@example +@group +(defun f (number) + (lazy-let ((derived-number + (progn (message "Calculating 1 plus 2 times %d" number) + (1+ (* 2 number))))) + (if (> number 10) + derived-number + number))) +@end group + +@group +(f 5) +@result{} 5 +@end group + +@group +(f 12) +@print{} "Calculating 1 plus 2 times 12" +25 +@end group + +@end example + +Because of the special nature of lazily bound variables, it is an error +to set them (e.g.@ with @code{setq}). + + +@defmac lazy-let* (bindings...) forms... +This is like @code{lazy-let} but any expression in @var{bindings} is allowed +to refer to preceding bindings in this @code{lazy-let*} form. Using +this macro requires @code{lexical-binding}. +@end defmac + +@example +@group +(lazy-let* ((x (prog2 (message "Calculating x...") + (+ 1 1) + (message "Finished calculating x"))) + (y (prog2 (message "Calculating y...") + (+ x 1) + (message "Finished calculating y"))) + (z (prog2 (message "Calculating z...") + (+ y 1) + (message "Finished calculating z"))) + (a (prog2 (message "Calculating a...") + (+ z 1) + (message "Finished calculating a")))) + (* z x)) + +@print{} Calculating z... +@print{} Calculating y... +@print{} Calculating x... +@print{} Finished calculating x +@print{} Finished calculating y +@print{} Finished calculating z +@result{} 8 + +@end group +@end example + +@code{lazy-let} and @code{lazy-let*} use thunks implicitly: their +expansion creates helper symbols and binds them to thunks wrapping the +binding expressions. All references to the original variables in the +body @var{forms} are then replaced by an expression that calls +@code{thunk-force} on the according helper variable. So, any code +using @code{lazy-let} or @code{lazy-let*} could be rewritten to use +thunks, but in many cases using these macros results in nicer code +than using thunks explicitly. diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..bfa8be39f9 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -109,6 +109,10 @@ Snake and Pong are more playable on HiDPI displays. *** Completing filenames in the minibuffer via 'C-TAB' now uses the styles as configured by the variable 'completion-styles'. +** Thunk +*** The new macros 'thunk-let' and 'thunk-let*' are analogue to `let' +and `let*' but create bindings that are evaluated lazily. + \f * New Modes and Packages in Emacs 27.1 diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el index 371d10444b..9cb16857f4 100644 --- a/lisp/emacs-lisp/thunk.el +++ b/lisp/emacs-lisp/thunk.el @@ -41,6 +41,10 @@ ;; following: ;; ;; (thunk-force delayed) +;; +;; This file also defines macros `thunk-let' and `thunk-let*' that are +;; analogous to `let' and `let*' but provide lazy evaluation of +;; bindings by using thunks implicitly (i.e. in the expansion). ;;; Code: @@ -71,5 +75,57 @@ thunk-evaluated-p "Return non-nil if DELAYED has been evaluated." (funcall delayed t)) +(defmacro thunk-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in thunk-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body))) + +(defmacro thunk-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(thunk-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + + (provide 'thunk) ;;; thunk.el ends here diff --git a/test/lisp/emacs-lisp/thunk-tests.el b/test/lisp/emacs-lisp/thunk-tests.el index 973a14b818..8e5c8fd4d5 100644 --- a/test/lisp/emacs-lisp/thunk-tests.el +++ b/test/lisp/emacs-lisp/thunk-tests.el @@ -51,5 +51,55 @@ (thunk-force thunk) (should (= x 1)))) + +\f +;; thunk-let tests + +(ert-deftest thunk-let-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest thunk-let*-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest thunk-let-bound-vars-cant-be-set-test () + "Test whether setting a `thunk-let' bound variable fails." + (should-error + (eval '(thunk-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t))) + +(ert-deftest thunk-let-laziness-test () + "Test laziness of `thunk-let'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (thunk-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest thunk-let*-laziness-test () + "Test laziness of `thunk-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (thunk-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest thunk-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (macroexpand '(thunk-let ((x 1 1)) x))) + (should-error (macroexpand '(thunk-let (27) x))) + (should-error (macroexpand '(thunk-let x x)))) + + (provide 'thunk-tests) ;;; thunk-tests.el ends here -- 2.15.0 [-- Attachment #3: Type: text/plain, Size: 21 bytes --] Thanks, Michael. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0001-Add-macros-thunk-let-and-thunk-let.patch --] [-- Type: text/x-diff, Size: 11083 bytes --] From 6d7028defb30b403505a72ecf68ac52fe478d562 Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `thunk-let' and `thunk-let*' * lisp/emacs-lisp/thunk.el (thunk-let, thunk-let*): New macros. * test/lisp/emacs-lisp/thunk-tests.el: (thunk-let-basic-test, thunk-let*-basic-test) (thunk-let-bound-vars-cant-be-set-test) (thunk-let-laziness-test, thunk-let*-laziness-test) (thunk-let-bad-binding-test): New tests for `thunk-let' and `thunk-let*. * doc/lispref/eval.texi (Deferred Eval): New section. * doc/lispref/elisp.texi: Update menu. --- doc/lispref/elisp.texi | 1 + doc/lispref/eval.texi | 118 ++++++++++++++++++++++++++++++++++-- etc/NEWS | 4 ++ lisp/emacs-lisp/thunk.el | 56 +++++++++++++++++ test/lisp/emacs-lisp/thunk-tests.el | 50 +++++++++++++++ 5 files changed, 224 insertions(+), 5 deletions(-) diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index c752594584..a271749e04 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -455,6 +455,7 @@ Top the program). * Backquote:: Easier construction of list structure. * Eval:: How to invoke the Lisp interpreter explicitly. +* Deferred Eval:: Deferred and lazy evaluation of forms. Kinds of Forms diff --git a/doc/lispref/eval.texi b/doc/lispref/eval.texi index 064fca22ff..8b61e39142 100644 --- a/doc/lispref/eval.texi +++ b/doc/lispref/eval.texi @@ -20,11 +20,12 @@ Evaluation @ifnottex @menu -* Intro Eval:: Evaluation in the scheme of things. -* Forms:: How various sorts of objects are evaluated. -* Quoting:: Avoiding evaluation (to put constants in the program). -* Backquote:: Easier construction of list structure. -* Eval:: How to invoke the Lisp interpreter explicitly. +* Intro Eval:: Evaluation in the scheme of things. +* Forms:: How various sorts of objects are evaluated. +* Quoting:: Avoiding evaluation (to put constants in the program). +* Backquote:: Easier construction of list structure. +* Eval:: How to invoke the Lisp interpreter explicitly. +* Deferred Eval:: Deferred and lazy evaluation of forms @end menu @node Intro Eval @@ -877,3 +878,110 @@ Eval @end group @end example @end defvar + +@node Deferred Eval +@section Deferred and Lazy Evaluation + +@cindex deferred evaluation +@cindex lazy evaluation + + + Sometimes it is useful to delay the evaluation of an expression, for +example if you want to avoid to perform a time-consuming calculation +in the case that it turns out that the result is not needed in the +future of the program. + + +@defmac thunk-delay forms... +Return a thunk for evaluating the @var{forms}. A thunk is a closure +that evaluates the @var{forms} in the lexical environment present when +@code{thunk-delay} had been called. Using this macro requires +@code{lexical-binding}. +@end defmac + +@defun thunk-force thunk +Force @var{thunk} to perform the evaluation of the forms specified to the +@code{thunk-delay} that created the thunk. The result of the evaluation +of the last form is returned. The @var{thunk} also "remembers" that it has +been forced: Any further calls of @code{thunk-force} on the same @var{thunk} +will just return the same result without evaluating the @var{forms} again. +@end defun + +@defmac lazy-let (bindings...) forms... +This macro is analogous to @code{let} but creates "lazy" variable +bindings. Any binding has the form (@var{symbol} @var{value-form}). +Unlike @code{let}, the evaluation of any @var{value-form} is deferred +until the binding of the according @var{symbol} is used for the first +time when evaluating the @var{forms}. Any @var{value-form} is evaluated +at most once. Using this macro requires @code{lexical-binding}. +@end defmac + +@example +@group +(defun f (number) + (lazy-let ((derived-number + (progn (message "Calculating 1 plus 2 times %d" number) + (1+ (* 2 number))))) + (if (> number 10) + derived-number + number))) +@end group + +@group +(f 5) +@result{} 5 +@end group + +@group +(f 12) +@print{} "Calculating 1 plus 2 times 12" +25 +@end group + +@end example + +Because of the special nature of lazily bound variables, it is an error +to set them (e.g.@ with @code{setq}). + + +@defmac lazy-let* (bindings...) forms... +This is like @code{lazy-let} but any expression in @var{bindings} is allowed +to refer to preceding bindings in this @code{lazy-let*} form. Using +this macro requires @code{lexical-binding}. +@end defmac + +@example +@group +(lazy-let* ((x (prog2 (message "Calculating x...") + (+ 1 1) + (message "Finished calculating x"))) + (y (prog2 (message "Calculating y...") + (+ x 1) + (message "Finished calculating y"))) + (z (prog2 (message "Calculating z...") + (+ y 1) + (message "Finished calculating z"))) + (a (prog2 (message "Calculating a...") + (+ z 1) + (message "Finished calculating a")))) + (* z x)) + +@print{} Calculating z... +@print{} Calculating y... +@print{} Calculating x... +@print{} Finished calculating x +@print{} Finished calculating y +@print{} Finished calculating z +@result{} 8 + +@end group +@end example + +@code{lazy-let} and @code{lazy-let*} use thunks implicitly: their +expansion creates helper symbols and binds them to thunks wrapping the +binding expressions. All references to the original variables in the +body @var{forms} are then replaced by an expression that calls +@code{thunk-force} on the according helper variable. So, any code +using @code{lazy-let} or @code{lazy-let*} could be rewritten to use +thunks, but in many cases using these macros results in nicer code +than using thunks explicitly. diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..bfa8be39f9 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -109,6 +109,10 @@ Snake and Pong are more playable on HiDPI displays. *** Completing filenames in the minibuffer via 'C-TAB' now uses the styles as configured by the variable 'completion-styles'. +** Thunk +*** The new macros 'thunk-let' and 'thunk-let*' are analogue to `let' +and `let*' but create bindings that are evaluated lazily. + \f * New Modes and Packages in Emacs 27.1 diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el index 371d10444b..9cb16857f4 100644 --- a/lisp/emacs-lisp/thunk.el +++ b/lisp/emacs-lisp/thunk.el @@ -41,6 +41,10 @@ ;; following: ;; ;; (thunk-force delayed) +;; +;; This file also defines macros `thunk-let' and `thunk-let*' that are +;; analogous to `let' and `let*' but provide lazy evaluation of +;; bindings by using thunks implicitly (i.e. in the expansion). ;;; Code: @@ -71,5 +75,57 @@ thunk-evaluated-p "Return non-nil if DELAYED has been evaluated." (funcall delayed t)) +(defmacro thunk-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in thunk-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body))) + +(defmacro thunk-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(thunk-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + + (provide 'thunk) ;;; thunk.el ends here diff --git a/test/lisp/emacs-lisp/thunk-tests.el b/test/lisp/emacs-lisp/thunk-tests.el index 973a14b818..8e5c8fd4d5 100644 --- a/test/lisp/emacs-lisp/thunk-tests.el +++ b/test/lisp/emacs-lisp/thunk-tests.el @@ -51,5 +51,55 @@ (thunk-force thunk) (should (= x 1)))) + +\f +;; thunk-let tests + +(ert-deftest thunk-let-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest thunk-let*-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest thunk-let-bound-vars-cant-be-set-test () + "Test whether setting a `thunk-let' bound variable fails." + (should-error + (eval '(thunk-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t))) + +(ert-deftest thunk-let-laziness-test () + "Test laziness of `thunk-let'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (thunk-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest thunk-let*-laziness-test () + "Test laziness of `thunk-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (thunk-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest thunk-let-bad-binding-test () + "Test whether a bad binding causes a compiler error." + (should-error (macroexpand '(thunk-let ((x 1 1)) x))) + (should-error (macroexpand '(thunk-let (27) x))) + (should-error (macroexpand '(thunk-let x x)))) + + (provide 'thunk-tests) ;;; thunk-tests.el ends here -- 2.15.0 ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-23 4:15 ` `thunk-let'? Michael Heerdegen @ 2017-11-23 16:34 ` Pip Cet 2017-11-23 23:41 ` `thunk-let'? Michael Heerdegen 2017-11-24 8:36 ` `thunk-let'? Eli Zaretskii 1 sibling, 1 reply; 77+ messages in thread From: Pip Cet @ 2017-11-23 16:34 UTC (permalink / raw) To: Michael Heerdegen; +Cc: Eli Zaretskii, emacs-devel Unless I'm missing something (there appear to be two attachments to that last email), you define thunk-let and thunk-let*, but document lazy-let and lazy-let*. IMHO, lazy-let is much better as a name: thunk-let doesn't describe what it does, only how it's implemented; more importantly, JavaScript-like Promises could also be implemented with thunks, so it's ambiguous. (The difference is that JavaScript promises get evaluated at some point in the future and their result is then made available; lazy variables are never evaluated unless they're actually needed.) On Thu, Nov 23, 2017 at 4:15 AM, Michael Heerdegen <michael_heerdegen@web.de> wrote: > Michael Heerdegen <michael_heerdegen@web.de> writes: > >> This helped a lot. My first version even compiled without error. I'll >> post the updated patch soon. > > Ok, here is what I have, with everything discusses included (see also > attached). I will proofread it once more, maybe others will want to > have a look in the meantime, too. > > > > > Thanks, > > Michael. > > ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-23 16:34 ` `thunk-let'? Pip Cet @ 2017-11-23 23:41 ` Michael Heerdegen 2017-11-24 8:37 ` `thunk-let'? Eli Zaretskii 0 siblings, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-23 23:41 UTC (permalink / raw) To: Pip Cet; +Cc: Eli Zaretskii, emacs-devel Pip Cet <pipcet@gmail.com> writes: > Unless I'm missing something (there appear to be two attachments to > that last email), There was one inline for reading, and one attached for testing. > you define thunk-let and thunk-let*, but document lazy-let and > lazy-let*. Oops. Sounds the same for me right now. Thanks for noticing that. > IMHO, lazy-let is much better as a name: thunk-let doesn't describe > what it does, only how it's implemented; more importantly, > JavaScript-like Promises could also be implemented with thunks, so > it's ambiguous. What do others think about this? I had already asked if making `lazy-let' an alias to `thunk-let' would be ok (package prefix rule), but nobody had answered. A second question is: Do we really want to have the library have no autoloads? It's, at least, a bit unusual to have these things prominently described in the manual, and you have to require the library explicitly (if we keep it like this, I would have to add a note to the manual that you must require the library in order to use the described stuff). Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-23 23:41 ` `thunk-let'? Michael Heerdegen @ 2017-11-24 8:37 ` Eli Zaretskii 2017-11-24 8:51 ` `thunk-let'? Stefan Monnier 2017-11-27 5:21 ` `thunk-let'? Michael Heerdegen 0 siblings, 2 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-24 8:37 UTC (permalink / raw) To: Michael Heerdegen; +Cc: pipcet, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org > Date: Fri, 24 Nov 2017 00:41:47 +0100 > > > IMHO, lazy-let is much better as a name: thunk-let doesn't describe > > what it does, only how it's implemented; more importantly, > > JavaScript-like Promises could also be implemented with thunks, so > > it's ambiguous. > > What do others think about this? I had already asked if making > `lazy-let' an alias to `thunk-let' would be ok (package prefix rule), > but nobody had answered. My opinion about this doesn't necessarily mean much, but I think such n alias could be a good thing. > A second question is: Do we really want to have the library have no > autoloads? I see no reason not to autoload these. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-24 8:37 ` `thunk-let'? Eli Zaretskii @ 2017-11-24 8:51 ` Stefan Monnier 2017-11-24 9:16 ` `thunk-let'? Eli Zaretskii 2017-11-27 5:21 ` `thunk-let'? Michael Heerdegen 1 sibling, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-11-24 8:51 UTC (permalink / raw) To: emacs-devel >> A second question is: Do we really want to have the library have no >> autoloads? > I see no reason not to autoload these. Well, it's pretty easy for those few Elisp file which use it to (require 'thunk), so I'm not sure it's worth the hassle of autoloading. Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-24 8:51 ` `thunk-let'? Stefan Monnier @ 2017-11-24 9:16 ` Eli Zaretskii 2017-11-24 13:33 ` `thunk-let'? Stefan Monnier 0 siblings, 1 reply; 77+ messages in thread From: Eli Zaretskii @ 2017-11-24 9:16 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Date: Fri, 24 Nov 2017 03:51:20 -0500 > > >> A second question is: Do we really want to have the library have no > >> autoloads? > > I see no reason not to autoload these. > > Well, it's pretty easy for those few Elisp file which use it to > (require 'thunk), so I'm not sure it's worth the hassle of autoloading. What's the "hassle"? ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-24 9:16 ` `thunk-let'? Eli Zaretskii @ 2017-11-24 13:33 ` Stefan Monnier 0 siblings, 0 replies; 77+ messages in thread From: Stefan Monnier @ 2017-11-24 13:33 UTC (permalink / raw) To: emacs-devel >> >> A second question is: Do we really want to have the library have no >> >> autoloads? >> > I see no reason not to autoload these. >> Well, it's pretty easy for those few Elisp file which use it to >> (require 'thunk), so I'm not sure it's worth the hassle of autoloading. > What's the "hassle"? With libraries of functions like thunk.el, the hassles have to do with the fact that almost all functions need to be autoloaded, that compilation can generate invalid code if the autoloads were not yet in place (so macros failed to be macro-expanded), the cost of the autoloads themselves, ... These are minor, so if/when thunk.el is used by a large portion of Elisp packages, these issues aren't significant, but given that it's currently used by ... only 2 packages so far AFAICT (el-search and stream), I really don't see any justification for autoloading its functions: the cost is low but the benefit is even lower. Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-24 8:37 ` `thunk-let'? Eli Zaretskii 2017-11-24 8:51 ` `thunk-let'? Stefan Monnier @ 2017-11-27 5:21 ` Michael Heerdegen 2017-11-27 13:34 ` `thunk-let'? Stefan Monnier 1 sibling, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-27 5:21 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Stefan Monnier, pipcet, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > My opinion about this doesn't necessarily mean much, but I think such > n alias could be a good thing. Hmm - Would it even be acceptable to make `lazy-let' and `lazy-let*' the only names? It would be strange if we would provide `lazy-let' and `lazy-let*' as alias names because we think they are actually the more suitable names, and present the macros in the manual as `thunk-let' and `thunk-let*'. OTOH, if we call them `lazy-let' and `lazy-let*' in the docs and everywhere, it would be strange to define them as `lazy-let' and `lazy-let*' in the first place, and then nobody really uses these names. Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-27 5:21 ` `thunk-let'? Michael Heerdegen @ 2017-11-27 13:34 ` Stefan Monnier 2017-11-27 15:44 ` `thunk-let'? Eli Zaretskii 0 siblings, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-11-27 13:34 UTC (permalink / raw) To: emacs-devel > Hmm - Would it even be acceptable to make `lazy-let' and `lazy-let*' the > only names? I think you're over-thinking it. If I were you, I'd go with thunk-let which doesn't need any authorization/argument, yet works well enough. If/when it turns out to become wildly popular or unacceptably confusing, we can add an alias and/or rename it. My expectation is that it won't be used very often anyway, so it doesn't need to escape the namespace of its home package. Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-27 13:34 ` `thunk-let'? Stefan Monnier @ 2017-11-27 15:44 ` Eli Zaretskii 2017-11-30 15:19 ` `thunk-let'? Michael Heerdegen 0 siblings, 1 reply; 77+ messages in thread From: Eli Zaretskii @ 2017-11-27 15:44 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Date: Mon, 27 Nov 2017 08:34:32 -0500 > > > Hmm - Would it even be acceptable to make `lazy-let' and `lazy-let*' the > > only names? > > I think you're over-thinking it. If I were you, I'd go with thunk-let > which doesn't need any authorization/argument, yet works well enough. > If/when it turns out to become wildly popular or unacceptably confusing, > we can add an alias and/or rename it. I agree. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-27 15:44 ` `thunk-let'? Eli Zaretskii @ 2017-11-30 15:19 ` Michael Heerdegen 0 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-30 15:19 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > > I think you're over-thinking it. If I were you, I'd go with > > thunk-let which doesn't need any authorization/argument, yet works > > well enough. If/when it turns out to become wildly popular or > > unacceptably confusing, we can add an alias and/or rename it. > > I agree. Ok, then let's do this. I've commented out the `defalias' calls. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-23 4:15 ` `thunk-let'? Michael Heerdegen 2017-11-23 16:34 ` `thunk-let'? Pip Cet @ 2017-11-24 8:36 ` Eli Zaretskii 2017-11-30 15:17 ` `thunk-let'? Michael Heerdegen 1 sibling, 1 reply; 77+ messages in thread From: Eli Zaretskii @ 2017-11-24 8:36 UTC (permalink / raw) To: Michael Heerdegen; +Cc: emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: emacs-devel@gnu.org > Date: Thu, 23 Nov 2017 05:15:44 +0100 > > Michael Heerdegen <michael_heerdegen@web.de> writes: > > > This helped a lot. My first version even compiled without error. I'll > > post the updated patch soon. > > Ok, here is what I have, with everything discusses included (see also > attached). I will proofread it once more, maybe others will want to > have a look in the meantime, too. My comments below. > +@defmac thunk-delay forms... Please use "forms@dots{}" instead, it produces a prettier ellipsis. > +Return a thunk for evaluating the @var{forms}. A thunk is a closure > +that evaluates the @var{forms} in the lexical environment present when > +@code{thunk-delay} had been called. This could be improved as follows: @cindex thunk Return a @dfn{thunk} for evaluating the @var{forms}. A thunk is a closure (@pxref{Closures}) that evaluates the @var{forms} in the lexical environment present when @code{thunk-delay} had been called. The improvements are: . you introduce a new term, so it should be in @dfn, and an index entry for it is in order . you mention "closures", so a cross-reference to where these are described is a Good Thing, because the reader might not know or remember what that is > +@code{thunk-delay} had been called. "had been called" or "will be called"? > +@defun thunk-force thunk > +Force @var{thunk} to perform the evaluation of the forms specified to the > +@code{thunk-delay} that created the thunk. "to the thunk-delay" or "in the thunk-delay" (or "for the thunk-delay")? > +of the last form is returned. The @var{thunk} also "remembers" that it has Quoting in Texinfo is ``like this'' (I guess you didn't use the Emacs Texinfo mode, or typed `C-q "' here.) makeinfo will then convert these to the actual quote characters. > +been forced: Any further calls of @code{thunk-force} on the same @var{thunk} ^^ "with" > +@defmac lazy-let (bindings...) forms... @dots{} again > +This macro is analogous to @code{let} but creates "lazy" variable ``lazy''. > +bindings. Any binding has the form (@var{symbol} @var{value-form}). The entire parenthesized expression should be in @code: @code{(@var{symbol} @var{value-form})} I also recommend to wrap that in @w, so that it doesn't get split between two lines, like this: @w{@code{(@var{symbol} @var{value-form})}} > +@group > +(f 12) > +@print{} "Calculating 1 plus 2 times 12" > +25 This 25 should have @result{} before it, right? > +to set them (e.g.@ with @code{setq}). You mean "e.g.@:", right? > +@defmac lazy-let* (bindings...) forms... @dots{} > +binding expressions. All references to the original variables in the > +body @var{forms} are then replaced by an expression that calls > +@code{thunk-force} on the according helper variable. So, any code "calls ... on" is slightly awkward English, I think. How about "calls thunk-force passing it the according helper variable as an argument", or calls thunk-force with the according helper variable as the argument" instead? > +** Thunk > +*** The new macros 'thunk-let' and 'thunk-let*' are analogue to `let' > +and `let*' but create bindings that are evaluated lazily. I think this should be a single-level entry: ** New macros 'thunk-let' and 'thunk-let*'. These macros are analogue to `let' and `let*', but create bindings that are evaluated lazily. Thanks again for working on this. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-24 8:36 ` `thunk-let'? Eli Zaretskii @ 2017-11-30 15:17 ` Michael Heerdegen 2017-11-30 16:06 ` `thunk-let'? Eli Zaretskii 0 siblings, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-30 15:17 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 1395 bytes --] Hi Eli, > My comments below. thanks for the review. I've incorporated all your comments, see the attached patch for the result. > This could be improved as follows: > > @cindex thunk > Return a @dfn{thunk} for evaluating the @var{forms}. A thunk is a > closure (@pxref{Closures}) that evaluates the @var{forms} in the > lexical environment present when @code{thunk-delay} had been called. Done (but I've moved the cindex before the defmac). > > +@code{thunk-delay} had been called. > > "had been called" or "will be called"? "Had", I think, but I've rephrased that as follows: A thunk is a closure (@pxref{Closures}) that inherits the lexical environment of the +@code{thunk-delay} call. > > @defun thunk-force thunk > > Force @var{thunk} to perform the evaluation > > of the forms specified to the +@code{thunk-delay} that created the > > thunk. > > "to the thunk-delay" or "in the thunk-delay" (or "for the thunk-delay")? I hope "in" is the right preposition? > Quoting in Texinfo is ``like this'' (I guess you didn't use the Emacs > Texinfo mode, or typed `C-q "' here.) It was a pilot error - I had hit the key twice to get the "right" quoting :-P - fixed. > > +@group > > +(f 12) > > +@print{} "Calculating 1 plus 2 times 12" > > +25 > > This 25 should have @result{} before it, right? Right. I also removed the quotes in the "print" line. Thanks, Michael. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Add-macros-thunk-let-and-thunk-let.patch --] [-- Type: text/x-diff, Size: 11349 bytes --] From 66723e903caff1067a356cbc71a55aa7825579ab Mon Sep 17 00:00:00 2001 From: Michael Heerdegen <michael_heerdegen@web.de> Date: Thu, 2 Nov 2017 18:45:34 +0100 Subject: [PATCH] Add macros `thunk-let' and `thunk-let*' * lisp/emacs-lisp/thunk.el (thunk-let, thunk-let*): New macros. * test/lisp/emacs-lisp/thunk-tests.el: (thunk-let-basic-test, thunk-let*-basic-test) (thunk-let-bound-vars-cant-be-set-test) (thunk-let-laziness-test, thunk-let*-laziness-test) (thunk-let-bad-binding-test): New tests for `thunk-let' and `thunk-let*. * doc/lispref/eval.texi (Deferred Eval): New section. * doc/lispref/elisp.texi: Update menu. --- doc/lispref/elisp.texi | 1 + doc/lispref/eval.texi | 123 ++++++++++++++++++++++++++++++++++-- etc/NEWS | 4 ++ lisp/emacs-lisp/thunk.el | 59 +++++++++++++++++ test/lisp/emacs-lisp/thunk-tests.el | 50 +++++++++++++++ 5 files changed, 232 insertions(+), 5 deletions(-) diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index c752594584..a271749e04 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -455,6 +455,7 @@ Top the program). * Backquote:: Easier construction of list structure. * Eval:: How to invoke the Lisp interpreter explicitly. +* Deferred Eval:: Deferred and lazy evaluation of forms. Kinds of Forms diff --git a/doc/lispref/eval.texi b/doc/lispref/eval.texi index 064fca22ff..2a464ffd31 100644 --- a/doc/lispref/eval.texi +++ b/doc/lispref/eval.texi @@ -20,11 +20,12 @@ Evaluation @ifnottex @menu -* Intro Eval:: Evaluation in the scheme of things. -* Forms:: How various sorts of objects are evaluated. -* Quoting:: Avoiding evaluation (to put constants in the program). -* Backquote:: Easier construction of list structure. -* Eval:: How to invoke the Lisp interpreter explicitly. +* Intro Eval:: Evaluation in the scheme of things. +* Forms:: How various sorts of objects are evaluated. +* Quoting:: Avoiding evaluation (to put constants in the program). +* Backquote:: Easier construction of list structure. +* Eval:: How to invoke the Lisp interpreter explicitly. +* Deferred Eval:: Deferred and lazy evaluation of forms. @end menu @node Intro Eval @@ -877,3 +878,115 @@ Eval @end group @end example @end defvar + +@node Deferred Eval +@section Deferred and Lazy Evaluation + +@cindex deferred evaluation +@cindex lazy evaluation + + + Sometimes it is useful to delay the evaluation of an expression, for +example if you want to avoid to perform a time-consuming calculation +in the case that it turns out that the result is not needed in the +future of the program. Therefore, the @file{thunk} library provides +the following functions and macros: + +@cindex thunk +@defmac thunk-delay forms@dots{} +Return a @dfn{thunk} for evaluating the @var{forms}. A thunk is a +closure (@pxref{Closures}) that inherits the lexical enviroment of the +@code{thunk-delay} call. Using this macro requires +@code{lexical-binding}. +@end defmac + +@defun thunk-force thunk +Force @var{thunk} to perform the evaluation of the forms specified in +the @code{thunk-delay} that created the thunk. The result of the +evaluation of the last form is returned. The @var{thunk} also +``remembers'' that it has been forced: Any further calls of +@code{thunk-force} with the same @var{thunk} will just return the same +result without evaluating the forms again. +@end defun + +@defmac thunk-let (bindings@dots{}) forms@dots{} +This macro is analogous to @code{let} but creates ``lazy'' variable +bindings. Any binding has the form @w{@code{(@var{symbol} +@var{value-form})}}. Unlike @code{let}, the evaluation of any +@var{value-form} is deferred until the binding of the according +@var{symbol} is used for the first time when evaluating the +@var{forms}. Any @var{value-form} is evaluated at most once. Using +this macro requires @code{lexical-binding}. +@end defmac + +Example: + +@example +@group +(defun f (number) + (thunk-let ((derived-number + (progn (message "Calculating 1 plus 2 times %d" number) + (1+ (* 2 number))))) + (if (> number 10) + derived-number + number))) +@end group + +@group +(f 5) +@result{} 5 +@end group + +@group +(f 12) +@print{} "Calculating 1 plus 2 times 12" +@result{} 25 +@end group + +@end example + +Because of the special nature of lazily bound variables, it is an error +to set them (e.g.@: with @code{setq}). + + +@defmac thunk-let* (bindings@dots{}) forms@dots{} +This is like @code{thunk-let} but any expression in @var{bindings} is allowed +to refer to preceding bindings in this @code{thunk-let*} form. Using +this macro requires @code{lexical-binding}. +@end defmac + +@example +@group +(thunk-let* ((x (prog2 (message "Calculating x...") + (+ 1 1) + (message "Finished calculating x"))) + (y (prog2 (message "Calculating y...") + (+ x 1) + (message "Finished calculating y"))) + (z (prog2 (message "Calculating z...") + (+ y 1) + (message "Finished calculating z"))) + (a (prog2 (message "Calculating a...") + (+ z 1) + (message "Finished calculating a")))) + (* z x)) + +@print{} Calculating z... +@print{} Calculating y... +@print{} Calculating x... +@print{} Finished calculating x +@print{} Finished calculating y +@print{} Finished calculating z +@result{} 8 + +@end group +@end example + +@code{thunk-let} and @code{thunk-let*} use thunks implicitly: their +expansion creates helper symbols and binds them to thunks wrapping the +binding expressions. All references to the original variables in the +body @var{forms} are then replaced by an expression that calls +@code{thunk-force} with the according helper variable as the argument. +So, any code using @code{thunk-let} or @code{thunk-let*} could be +rewritten to use thunks, but in many cases using these macros results +in nicer code than using thunks explicitly. diff --git a/etc/NEWS b/etc/NEWS index c47ca42d27..3343ecf024 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -109,6 +109,10 @@ Snake and Pong are more playable on HiDPI displays. *** Completing filenames in the minibuffer via 'C-TAB' now uses the styles as configured by the variable 'completion-styles'. +** New macros 'thunk-let' and 'thunk-let*'. +These macros are analogue to `let' and `let*', but create bindings that +are evaluated lazily. + \f * New Modes and Packages in Emacs 27.1 diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el index 371d10444b..895fa86722 100644 --- a/lisp/emacs-lisp/thunk.el +++ b/lisp/emacs-lisp/thunk.el @@ -41,6 +41,10 @@ ;; following: ;; ;; (thunk-force delayed) +;; +;; This file also defines macros `thunk-let' and `thunk-let*' that are +;; analogous to `let' and `let*' but provide lazy evaluation of +;; bindings by using thunks implicitly (i.e. in the expansion). ;;; Code: @@ -71,5 +75,60 @@ thunk-evaluated-p "Return non-nil if DELAYED has been evaluated." (funcall delayed t)) +(defmacro thunk-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in thunk-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body))) + +(defmacro thunk-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(thunk-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + +;; (defalias 'lazy-let #'thunk-let) +;; (defalias 'lazy-let* #'thunk-let*) + + (provide 'thunk) ;;; thunk.el ends here diff --git a/test/lisp/emacs-lisp/thunk-tests.el b/test/lisp/emacs-lisp/thunk-tests.el index 973a14b818..a63ce289e8 100644 --- a/test/lisp/emacs-lisp/thunk-tests.el +++ b/test/lisp/emacs-lisp/thunk-tests.el @@ -51,5 +51,55 @@ (thunk-force thunk) (should (= x 1)))) + +\f +;; thunk-let tests + +(ert-deftest thunk-let-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let ((x 1) (y 2)) (+ x y)) 3))) + +(ert-deftest thunk-let*-basic-test () + "Test whether bindings are established." + (should (equal (thunk-let* ((x 1) (y (+ 1 x))) (+ x y)) 3))) + +(ert-deftest thunk-let-bound-vars-cant-be-set-test () + "Test whether setting a `thunk-let' bound variable fails." + (should-error + (eval '(thunk-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t))) + +(ert-deftest thunk-let-laziness-test () + "Test laziness of `thunk-let'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil)) + (thunk-let ((x (progn (setq x-evalled t) (+ 1 2))) + (y (progn (setq y-evalled t) (+ 3 4)))) + (let ((evalled-y y)) + (list x-evalled y-evalled evalled-y)))) + (list nil t 7)))) + +(ert-deftest thunk-let*-laziness-test () + "Test laziness of `thunk-let*'." + (should + (equal (let ((x-evalled nil) + (y-evalled nil) + (z-evalled nil) + (a-evalled nil)) + (thunk-let* ((x (progn (setq x-evalled t) (+ 1 1))) + (y (progn (setq y-evalled t) (+ x 1))) + (z (progn (setq z-evalled t) (+ y 1))) + (a (progn (setq a-evalled t) (+ z 1)))) + (let ((evalled-z z)) + (list x-evalled y-evalled z-evalled a-evalled evalled-z)))) + (list t t t nil 4)))) + +(ert-deftest thunk-let-bad-binding-test () + "Test whether a bad binding causes an error when expanding." + (should-error (macroexpand '(thunk-let ((x 1 1)) x))) + (should-error (macroexpand '(thunk-let (27) x))) + (should-error (macroexpand '(thunk-let x x)))) + + (provide 'thunk-tests) ;;; thunk-tests.el ends here -- 2.15.0 ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-30 15:17 ` `thunk-let'? Michael Heerdegen @ 2017-11-30 16:06 ` Eli Zaretskii 2017-12-01 8:02 ` `thunk-let'? Michael Heerdegen 0 siblings, 1 reply; 77+ messages in thread From: Eli Zaretskii @ 2017-11-30 16:06 UTC (permalink / raw) To: Michael Heerdegen; +Cc: emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: emacs-devel@gnu.org > Date: Thu, 30 Nov 2017 16:17:30 +0100 > > thanks for the review. I've incorporated all your comments, see the > attached patch for the result. LGTM, thanks. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-30 16:06 ` `thunk-let'? Eli Zaretskii @ 2017-12-01 8:02 ` Michael Heerdegen 0 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-12-01 8:02 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > > From: Michael Heerdegen <michael_heerdegen@web.de> > > Cc: emacs-devel@gnu.org > > Date: Thu, 30 Nov 2017 16:17:30 +0100 > > > > thanks for the review. I've incorporated all your comments, see the > > attached patch for the result. > > LGTM, thanks. Great. Installed to master as cc58d4de56. Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-23 2:59 ` `thunk-let'? Michael Heerdegen 2017-11-23 4:15 ` `thunk-let'? Michael Heerdegen @ 2017-11-23 16:04 ` Eli Zaretskii 1 sibling, 0 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-23 16:04 UTC (permalink / raw) To: Michael Heerdegen; +Cc: emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: emacs-devel@gnu.org > Date: Thu, 23 Nov 2017 03:59:52 +0100 > > Eli Zaretskii <eliz@gnu.org> writes: > > > Here it is. Let me know if something in your plain-text draft is not > > covered. > > This helped a lot. My first version even compiled without error. I'll > post the updated patch soon. Thanks. > > Program names should be written as @command{prog}. > > If you send this little introduction to other people: it was not clear > to me what kind of "program" this refers to. Maybe replace the example > with something like @command{gzip}. Noted. > The only question that I wondered about was the significance of > whitespace, but it was easy to find out that it's collapsed. Yes, makeinfo refills the generated text. > And it also doesn't matter whether I indent the first line of a new > paragraph in the texi source or not, right? Yes. The indentation of the first paragraph is controlled by @firstparagraphindent; by default it is not indented. The indentation of other paragraphs is controlled by @paragraphindent, which defaults to 3. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 2:50 ` `thunk-let'? Michael Heerdegen 2017-11-22 3:43 ` `thunk-let'? Eli Zaretskii @ 2017-11-22 17:44 ` Gemini Lasswell 2017-11-22 18:04 ` `thunk-let'? Noam Postavsky ` (2 more replies) 1 sibling, 3 replies; 77+ messages in thread From: Gemini Lasswell @ 2017-11-22 17:44 UTC (permalink / raw) To: Michael Heerdegen Cc: Nicolas Petton, Eli Zaretskii, Stefan Monnier, Emacs Development Michael Heerdegen <michael_heerdegen@web.de> writes: > + (should-error (byte-compile (thunk-let ((x 1 1)) x))) > + (should-error (byte-compile (thunk-let (27) x))) > + (should-error (byte-compile (thunk-let x x)))) Since the thunk-let forms aren't quoted, should-error is catching the evaluation error of the thunk-let form before byte-compile is called. And to my knowledge byte-compile doesn't signal errors, just issues warnings. It's also better to have the erroneous forms be quoted so that they don't cause errors when using Edebug or Testcover on thunk-tests.el. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 17:44 ` `thunk-let'? Gemini Lasswell @ 2017-11-22 18:04 ` Noam Postavsky 2017-11-22 18:31 ` `thunk-let'? Michael Heerdegen 2017-11-22 18:29 ` `thunk-let'? Michael Heerdegen 2017-11-22 19:54 ` `thunk-let'? Stefan Monnier 2 siblings, 1 reply; 77+ messages in thread From: Noam Postavsky @ 2017-11-22 18:04 UTC (permalink / raw) To: Gemini Lasswell Cc: Michael Heerdegen, Nicolas Petton, Emacs Development, Eli Zaretskii, Stefan Monnier On Wed, Nov 22, 2017 at 12:44 PM, Gemini Lasswell <gazally@runbox.com> wrote: > Michael Heerdegen <michael_heerdegen@web.de> writes: > >> + (should-error (byte-compile (thunk-let ((x 1 1)) x))) >> + (should-error (byte-compile (thunk-let (27) x))) >> + (should-error (byte-compile (thunk-let x x)))) > > Since the thunk-let forms aren't quoted, should-error is catching the > evaluation error of the thunk-let form before byte-compile is called. > And to my knowledge byte-compile doesn't signal errors, just issues > warnings. Binding `byte-compile-debug' to non-nil should let it signal errors. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 18:04 ` `thunk-let'? Noam Postavsky @ 2017-11-22 18:31 ` Michael Heerdegen 0 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-22 18:31 UTC (permalink / raw) To: Noam Postavsky Cc: Gemini Lasswell, Nicolas Petton, Emacs Development, Eli Zaretskii, Stefan Monnier Noam Postavsky <npostavs@users.sourceforge.net> writes: > Binding `byte-compile-debug' to non-nil should let it signal errors. I've changed the test to #+begin_src emacs-lisp (ert-deftest thunk-let-bad-binding-test () "Test whether a bad binding causes a compiler error." (let ((byte-compile-debug t)) (should-error (byte-compile '(thunk-let ((x 1 1)) x))) (should-error (byte-compile '(thunk-let (27) x))) (should-error (byte-compile '(thunk-let x x))))) #+end_src Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 17:44 ` `thunk-let'? Gemini Lasswell 2017-11-22 18:04 ` `thunk-let'? Noam Postavsky @ 2017-11-22 18:29 ` Michael Heerdegen 2017-11-22 19:54 ` `thunk-let'? Stefan Monnier 2 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-22 18:29 UTC (permalink / raw) To: Gemini Lasswell Cc: Nicolas Petton, Eli Zaretskii, Stefan Monnier, Emacs Development Gemini Lasswell <gazally@runbox.com> writes: > Since the thunk-let forms aren't quoted, should-error is catching the > evaluation error of the thunk-let form before byte-compile is called. Right, `byte-compile' is a function. Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 17:44 ` `thunk-let'? Gemini Lasswell 2017-11-22 18:04 ` `thunk-let'? Noam Postavsky 2017-11-22 18:29 ` `thunk-let'? Michael Heerdegen @ 2017-11-22 19:54 ` Stefan Monnier 2017-11-22 22:47 ` `thunk-let'? Michael Heerdegen 2 siblings, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-11-22 19:54 UTC (permalink / raw) To: Gemini Lasswell Cc: Michael Heerdegen, Nicolas Petton, Eli Zaretskii, Emacs Development >> + (should-error (byte-compile (thunk-let ((x 1 1)) x))) >> + (should-error (byte-compile (thunk-let (27) x))) >> + (should-error (byte-compile (thunk-let x x)))) I think you can solve the problems mentioned by not using `byte-compile` but something more precise. E.g. (should-error (macroexpand '(thunk-let ((x 1 1)) x))) -- Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-22 19:54 ` `thunk-let'? Stefan Monnier @ 2017-11-22 22:47 ` Michael Heerdegen 0 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-22 22:47 UTC (permalink / raw) To: Stefan Monnier; +Cc: Emacs Development Stefan Monnier <monnier@IRO.UMontreal.CA> writes: > >> + (should-error (byte-compile (thunk-let ((x 1 1)) x))) > >> + (should-error (byte-compile (thunk-let (27) x))) > >> + (should-error (byte-compile (thunk-let x x)))) > > I think you can solve the problems mentioned by not using `byte-compile` > but something more precise. E.g. > > (should-error (macroexpand '(thunk-let ((x 1 1)) x))) Ok, done. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-11-09 15:14 ` Michael Heerdegen 2017-11-09 18:39 ` `thunk-let'? Michael Heerdegen @ 2017-11-10 10:01 ` Eli Zaretskii 1 sibling, 0 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-10 10:01 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Date: Thu, 09 Nov 2017 16:14:45 +0100 > Cc: Nicolas Petton <nicolas@petton.fr>, Emacs Development <emacs-devel@gnu.org> > > > +;;;###autoload > > > (defmacro thunk-delay (&rest body) > > > > Maybe a better option is to add a `require` in the expansion of lazy-let? > > That should be fine, so I've done it as well. And how about moving these into thunk.el? I have no further comments, thanks for working on this. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-11-08 17:22 ` Michael Heerdegen 2017-11-08 18:02 ` Stefan Monnier @ 2017-11-08 18:04 ` Eli Zaretskii 2017-11-08 22:22 ` `thunk-let'? Michael Heerdegen ` (2 more replies) 1 sibling, 3 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-08 18:04 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Date: Wed, 08 Nov 2017 18:22:51 +0100 > Cc: Nicolas Petton <nicolas@petton.fr>, Emacs Development <emacs-devel@gnu.org> Thanks. I have a few comments to the documentation parts: > +--- > +** The new macros 'lazy-let' and 'lazy-let*' are analogue to `let' and > +`let*' but create bindings that are evaluated lazily. Given that "lazy evaluation" seems not to be described anywhere in the ELisp manual, I think we cannot get away with "---" here, and will have to add at least something to the manual. > +(defmacro lazy-let (bindings &rest body) > + "Like `let' but make delayed bindings. And this seems to use a different term for this. > +This is like `let' but any binding expression is not evaluated > +before the variable is used for the first time. The "like let" part is a repetition of what was already said in the first sentence, so there's no need to repeat it. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-08 18:04 ` Eli Zaretskii @ 2017-11-08 22:22 ` Michael Heerdegen 2017-11-08 23:06 ` `thunk-let'? Drew Adams 2017-11-09 17:20 ` `thunk-let'? Eli Zaretskii 2017-11-09 14:34 ` [SUSPECTED SPAM] `thunk-let'? Michael Heerdegen 2017-11-09 15:19 ` Michael Heerdegen 2 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-08 22:22 UTC (permalink / raw) To: Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > Given that "lazy evaluation" seems not to be described anywhere in the > ELisp manual, I think we cannot get away with "---" here, and will > have to add at least something to the manual. Well, it's in subr-x because I'm not sure that it is yet a good idea to "advertize it so loudly" as Stefan uses to say. Nothing in subr-x is described in the manual. If we are sure that we want to document this right now in the manual, we could document it together with thunks. Though, I'm also not sure if thunks won't be replaced with something more advanced in the near future. > > +(defmacro lazy-let (bindings &rest body) > > + "Like `let' but make delayed bindings. > > And this seems to use a different term for this. Yes, I'll make this more consistent, thanks. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* RE: `thunk-let'? 2017-11-08 22:22 ` `thunk-let'? Michael Heerdegen @ 2017-11-08 23:06 ` Drew Adams 2017-11-09 17:20 ` `thunk-let'? Eli Zaretskii 1 sibling, 0 replies; 77+ messages in thread From: Drew Adams @ 2017-11-08 23:06 UTC (permalink / raw) To: Michael Heerdegen, Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel > Well, it's in subr-x because I'm not sure that it is yet a good idea to > "advertize it so loudly" as Stefan uses to say. Nothing in subr-x is > described in the manual. This is not a good approach, IMHO. Whether we should have a library that is for "half-baked" stuff is debatable. But even if it is decided to do that (why?), I don't think it makes sense to decide whether something gets documented in the manuals based on how well baked we think it is. That's a bad criterion to use (IMHO). And it invites things to fall through the cracks and not be documented even after they get improved. The usual criteria for deciding whether something gets documented in a manual should apply to half-baked features also. We should WANT users to try half-baked stuff that we deliver. That's how it gets improved. Not advertizing something just because it is still iffy is not advisable. An exception could exist for something that we think might prove dangerous in some way (e.g. possible data loss). But probably something like that should not be delivered at all - it should just continue to be tested and improved "internally" until it is not problematic. If something is TOO iffy then just don't deliver it. Don't use _doc_ as way of "hiding" something that is unsure. My "vote" is to move the stuff in subr.el to where it belongs, thing by thing, place by place, and to document any of it that we would normally document, with no concern about how well baked we might think it is. > If we are sure that we want to document this right now > in the manual... There should be no "right now" based on how finished something is. If we really feel that something is not ready for release then it should just be pulled from the release. If something is distributed then it should be supported normally. Just one opinion. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-08 22:22 ` `thunk-let'? Michael Heerdegen 2017-11-08 23:06 ` `thunk-let'? Drew Adams @ 2017-11-09 17:20 ` Eli Zaretskii 2017-11-09 17:39 ` `thunk-let'? Clément Pit-Claudel ` (2 more replies) 1 sibling, 3 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-09 17:20 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: nicolas@petton.fr, monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Wed, 08 Nov 2017 23:22:09 +0100 > > Eli Zaretskii <eliz@gnu.org> writes: > > > Given that "lazy evaluation" seems not to be described anywhere in the > > ELisp manual, I think we cannot get away with "---" here, and will > > have to add at least something to the manual. > > Well, it's in subr-x because I'm not sure that it is yet a good idea to > "advertize it so loudly" as Stefan uses to say. Nothing in subr-x is > described in the manual. I'm not sure this should be in subr-x.el. That file is AFAIU for stuff used _internally_ in Emacs, which this macro isn't. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 17:20 ` `thunk-let'? Eli Zaretskii @ 2017-11-09 17:39 ` Clément Pit-Claudel 2017-11-09 18:06 ` `thunk-let'? Michael Heerdegen 2017-11-09 18:14 ` `thunk-let'? Michael Heerdegen 2017-11-10 10:10 ` `thunk-let'? Nicolas Petton 2 siblings, 1 reply; 77+ messages in thread From: Clément Pit-Claudel @ 2017-11-09 17:39 UTC (permalink / raw) To: emacs-devel On 2017-11-09 12:20, Eli Zaretskii wrote: >> From: Michael Heerdegen <michael_heerdegen@web.de> >> Cc: nicolas@petton.fr, monnier@iro.umontreal.ca, emacs-devel@gnu.org >> Date: Wed, 08 Nov 2017 23:22:09 +0100 >> >> Eli Zaretskii <eliz@gnu.org> writes: >> >>> Given that "lazy evaluation" seems not to be described anywhere in the >>> ELisp manual, I think we cannot get away with "---" here, and will >>> have to add at least something to the manual. >> >> Well, it's in subr-x because I'm not sure that it is yet a good idea to >> "advertize it so loudly" as Stefan uses to say. Nothing in subr-x is >> described in the manual. > > I'm not sure this should be in subr-x.el. That file is AFAIU for > stuff used _internally_ in Emacs, which this macro isn't. Why doesn't that macro go to ELPA first, possibly to be integrated in Emacs proper if it's found to be useful? ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 17:39 ` `thunk-let'? Clément Pit-Claudel @ 2017-11-09 18:06 ` Michael Heerdegen 2017-11-09 21:05 ` `thunk-let'? Drew Adams 2017-11-09 21:48 ` `thunk-let'? Clément Pit-Claudel 0 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 18:06 UTC (permalink / raw) To: Clément Pit-Claudel; +Cc: emacs-devel Clément Pit-Claudel <cpitclaudel@gmail.com> writes: > Why doesn't that macro go to ELPA first, possibly to be integrated in > Emacs proper if it's found to be useful? I think it certainly is useful. And it allows you to write nicer code because you don't have to use (low-level) thunk objects explicitly (thus, it's a reasonable abstraction). I'm just only 97%, and not 100%, sure that it is the optimal solution for the problem it solves. So, "half baked" is an exaggeration. Anyway, the macro makes it much easier to write more efficient code in an easy way and clean style, so I don't doubt it is useful now, in Emacs. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* RE: `thunk-let'? 2017-11-09 18:06 ` `thunk-let'? Michael Heerdegen @ 2017-11-09 21:05 ` Drew Adams 2017-11-09 23:07 ` Sandbox subr-x? (was: `thunk-let'?) Michael Heerdegen 2017-11-09 21:48 ` `thunk-let'? Clément Pit-Claudel 1 sibling, 1 reply; 77+ messages in thread From: Drew Adams @ 2017-11-09 21:05 UTC (permalink / raw) To: Michael Heerdegen, Clément Pit-Claudel; +Cc: emacs-devel > I think it certainly is useful. And it allows you to write nicer code > because you don't have to use (low-level) thunk objects explicitly > (thus, it's a reasonable abstraction). I'm just only 97%, and not 100%, > sure that it is the optimal solution for the problem it solves. > > So, "half baked" is an exaggeration. Anyway, the macro makes it much > easier to write more efficient code in an easy way and clean style, so I > don't doubt it is useful now, in Emacs. FWIW: I'm the one who used the term "half-baked". But I did not mean to suggest that this contribution is in any way half-baked. Knowing your work a bit, Michael, I'd assume that this is not at all half-baked. (Not that there is anything wrong with stuff that is half-baked - it too can be useful.) I questioned only the purpose of having a library, apparently `subr.el', whose _purpose_ is to act as a sort of sandbox of stuff that, for whatever reason, someone doesn't consider quite ready for primetime. To me, whatever we deliver as part of Emacs should be considered supported (until it's not). If we're really worried about the quality of something then the choice is not to deliver it. If we deliver something to users we should put it in an appropriate file to begin with, not in a special "sandbox" file. And whether to document something in the manuals should not depend on how polished we think it might be. Other criteria (the usual ones) should decide inclusion in a manual. Those are the only points I meant to make, when I said: "Whether we should have a library that is for "half-baked" stuff is debatable." "I don't think it makes sense to decide whether something gets documented in the manuals based on how well baked we think it is." "We should WANT users to try half-baked stuff that we deliver. That's how it gets improved." ^ permalink raw reply [flat|nested] 77+ messages in thread
* Sandbox subr-x? (was: `thunk-let'?) 2017-11-09 21:05 ` `thunk-let'? Drew Adams @ 2017-11-09 23:07 ` Michael Heerdegen 2017-11-09 23:54 ` Drew Adams 2017-11-10 7:57 ` Eli Zaretskii 0 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 23:07 UTC (permalink / raw) To: Drew Adams; +Cc: Clément Pit-Claudel, emacs-devel Drew Adams <drew.adams@oracle.com> writes: > I questioned only the purpose of having a library, apparently > `subr.el', (I think you mean subr-x.) > whose _purpose_ is to act as a sort of sandbox of stuff that, for > whatever reason, someone doesn't consider quite ready for primetime. I don't have a strong opinion about it. But I think the approach works: interested people use this stuff - privately, but also in Emacs - and the stuff develops. Stuff that proved useful can later be moved to other places. OTOH, if you think about things that happened there (like `when-let' and `if-let' having been obsoleted and replaced by `if-let*', `when-let*' and `and-let*', not long after that had been added), it is not too bad that not every Emacs user and package developer had already used it. But maybe subr-x is also a symptom of limited manpower: if we had hordes of people volunteering writing and updating documentation, we would probably be less reluctant to fully integrate and document this stuff in the first place. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* RE: Sandbox subr-x? (was: `thunk-let'?) 2017-11-09 23:07 ` Sandbox subr-x? (was: `thunk-let'?) Michael Heerdegen @ 2017-11-09 23:54 ` Drew Adams 2017-11-10 7:57 ` Eli Zaretskii 1 sibling, 0 replies; 77+ messages in thread From: Drew Adams @ 2017-11-09 23:54 UTC (permalink / raw) To: Michael Heerdegen; +Cc: Clément Pit-Claudel, emacs-devel > > I questioned only the purpose of having a library, apparently > > `subr.el', > > (I think you mean subr-x.) Yes, I do; sorry. > > whose _purpose_ is to act as a sort of sandbox of stuff that, for > > whatever reason, someone doesn't consider quite ready for primetime. > > I don't have a strong opinion about it. But I think the approach works: > interested people use this stuff - privately, but also in Emacs - and > the stuff develops. Stuff that proved useful can later be moved to > other places. OTOH, if you think about things that happened there (like > `when-let' and `if-let' having been obsoleted and replaced by `if-let*', > `when-let*' and `and-let*', not long after that had been added), it is > not too bad that not every Emacs user and package developer had already > used it. If it works, it works. I can't argue much with that. I think that if Emacs dev thinks a sandbox is helpful then it should just use GNU ELPA, or a specific `sandbox' part of GNU ELPA, for that, instead of a single file, `subr-x.el' that is part of the distributed Emacs-Lisp code. Better to put stuff being experimented with in files with names related to that stuff, whether temporary or not. Having a single sack of anythings named `subr-x.el' is not a great idea. > But maybe subr-x is also a symptom of limited manpower: if we had hordes > of people volunteering writing and updating documentation, we would > probably be less reluctant to fully integrate and document this stuff in > the first place. That's not my position, at least. I don't think the current state of having a single `subr-x.el' sandbox delivered as part of Emacs follows from having limited manpower or would be finessed by having more manpower. (And something half-developed can also be half-documented...) ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: Sandbox subr-x? (was: `thunk-let'?) 2017-11-09 23:07 ` Sandbox subr-x? (was: `thunk-let'?) Michael Heerdegen 2017-11-09 23:54 ` Drew Adams @ 2017-11-10 7:57 ` Eli Zaretskii 1 sibling, 0 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-10 7:57 UTC (permalink / raw) To: Michael Heerdegen; +Cc: cpitclaudel, drew.adams, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Date: Fri, 10 Nov 2017 00:07:45 +0100 > Cc: Clément Pit-Claudel <cpitclaudel@gmail.com>, > emacs-devel@gnu.org > > But maybe subr-x is also a symptom of limited manpower: if we had hordes > of people volunteering writing and updating documentation, we would > probably be less reluctant to fully integrate and document this stuff in > the first place. No, I don't think this is the reason. We are lately trying to accompany every change with the appropriate documentation, and we are doing quite well, AFAICT, limited manpower notwithstanding. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 18:06 ` `thunk-let'? Michael Heerdegen 2017-11-09 21:05 ` `thunk-let'? Drew Adams @ 2017-11-09 21:48 ` Clément Pit-Claudel 2017-11-09 22:43 ` `thunk-let'? Michael Heerdegen 2017-11-10 7:48 ` `thunk-let'? Eli Zaretskii 1 sibling, 2 replies; 77+ messages in thread From: Clément Pit-Claudel @ 2017-11-09 21:48 UTC (permalink / raw) To: Michael Heerdegen; +Cc: emacs-devel On 2017-11-09 13:06, Michael Heerdegen wrote: > Clément Pit-Claudel <cpitclaudel@gmail.com> writes: > >> Why doesn't that macro go to ELPA first, possibly to be integrated in >> Emacs proper if it's found to be useful? > > I think it certainly is useful. And it allows you to write nicer code > because you don't have to use (low-level) thunk objects explicitly > (thus, it's a reasonable abstraction). I'm just only 97%, and not 100%, > sure that it is the optimal solution for the problem it solves. So, > "half baked" is an exaggeration. Anyway, the macro makes it much > easier to write more efficient code in an easy way and clean style, so I > don't doubt it is useful now, in Emacs. Thanks, but maybe you were responding to Drew and me at once? I didn't call the code half-baked :) I didn't even read it, in fact. I'm just trying to get a better feel for what goes straight into Emacs, and what goes into ELPA for experimentation. I was reacting to this: > Well, it's in subr-x because I'm not sure that it is yet a good idea to > "advertize it so loudly" as Stefan uses to say. That sounded a lot like the reason we usually invoke to put stuff into MELPA. Clément. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 21:48 ` `thunk-let'? Clément Pit-Claudel @ 2017-11-09 22:43 ` Michael Heerdegen 2017-11-10 7:48 ` `thunk-let'? Eli Zaretskii 1 sibling, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 22:43 UTC (permalink / raw) To: Clément Pit-Claudel; +Cc: emacs-devel Clément Pit-Claudel <cpitclaudel@gmail.com> writes: > Thanks, but maybe you were responding to Drew and me at once? Eh - yes, I did ;-) > I didn't call the code half-baked :) I didn't even read it, in > fact. I'm just trying to get a better feel for what goes straight into > Emacs, and what goes into ELPA for experimentation. I was reacting to > this: > > > Well, it's in subr-x because I'm not sure that it is yet a good idea to > > "advertize it so loudly" as Stefan uses to say. Yes, and it's good to discuss it. I don't have any experience with these decisions. I would say it's worth to support the basic concept of lazy bindings directly in Emacs, especially because the Elisp sources could benefit from it in the future. Unlike streams, for example, it's fundamental enough so that it could be useful not only for some special applications but for a broader set of files. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 21:48 ` `thunk-let'? Clément Pit-Claudel 2017-11-09 22:43 ` `thunk-let'? Michael Heerdegen @ 2017-11-10 7:48 ` Eli Zaretskii 1 sibling, 0 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-10 7:48 UTC (permalink / raw) To: Clément Pit-Claudel; +Cc: michael_heerdegen, emacs-devel > From: Clément Pit-Claudel <cpitclaudel@gmail.com> > Date: Thu, 9 Nov 2017 16:48:01 -0500 > Cc: emacs-devel@gnu.org > > > Well, it's in subr-x because I'm not sure that it is yet a good idea to > > "advertize it so loudly" as Stefan uses to say. > > That sounded a lot like the reason we usually invoke to put stuff into MELPA. Not for features used by Emacs internally. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 17:20 ` `thunk-let'? Eli Zaretskii 2017-11-09 17:39 ` `thunk-let'? Clément Pit-Claudel @ 2017-11-09 18:14 ` Michael Heerdegen 2017-11-09 20:26 ` `thunk-let'? Eli Zaretskii 2017-11-10 10:10 ` `thunk-let'? Nicolas Petton 2 siblings, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 18:14 UTC (permalink / raw) To: Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > I'm not sure this should be in subr-x.el. That file is AFAIU for > stuff used _internally_ in Emacs, which this macro isn't. It's a very basic binding construct. Albeit the internals are a bit more complicated, I think it is in the same class of macros as `when', for example. In which way is `lazy-let' less "internal" than `when'? I'm not sure I fully get what "internal" means in this context... Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 18:14 ` `thunk-let'? Michael Heerdegen @ 2017-11-09 20:26 ` Eli Zaretskii 2017-11-09 23:13 ` `thunk-let'? Michael Heerdegen 0 siblings, 1 reply; 77+ messages in thread From: Eli Zaretskii @ 2017-11-09 20:26 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: nicolas@petton.fr, monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Thu, 09 Nov 2017 19:14:06 +0100 > > > I'm not sure this should be in subr-x.el. That file is AFAIU for > > stuff used _internally_ in Emacs, which this macro isn't. > > It's a very basic binding construct. Albeit the internals are a bit > more complicated, I think it is in the same class of macros as `when', > for example. In which way is `lazy-let' less "internal" than `when'? > > I'm not sure I fully get what "internal" means in this context... It means not intended for use outside of Emacs internals. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 20:26 ` `thunk-let'? Eli Zaretskii @ 2017-11-09 23:13 ` Michael Heerdegen 2017-11-10 7:58 ` `thunk-let'? Eli Zaretskii 0 siblings, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 23:13 UTC (permalink / raw) To: Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > > I'm not sure I fully get what "internal" means in this context... > > It means not intended for use outside of Emacs internals. But no doubt `when', for example, is used outside of Emacs internals...? Anyway, I'm also ok to move the lazy lets to "thunk.el": it's also a natural place for it, and we all seem to be happier with this solution. Question: When I do that, is it allowed to keep `lazy-let' as an alias to `thunk-let' (despite the library prefix rule)? And I guess we would still want the addition to the manual? Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 23:13 ` `thunk-let'? Michael Heerdegen @ 2017-11-10 7:58 ` Eli Zaretskii 2017-11-11 15:20 ` `thunk-let'? Michael Heerdegen 0 siblings, 1 reply; 77+ messages in thread From: Eli Zaretskii @ 2017-11-10 7:58 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: nicolas@petton.fr, monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Fri, 10 Nov 2017 00:13:34 +0100 > > Eli Zaretskii <eliz@gnu.org> writes: > > > > I'm not sure I fully get what "internal" means in this context... > > > > It means not intended for use outside of Emacs internals. > > But no doubt `when', for example, is used outside of Emacs internals...? 'when' is in subr.el, not in subr-x.el. And its well documented. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-10 7:58 ` `thunk-let'? Eli Zaretskii @ 2017-11-11 15:20 ` Michael Heerdegen 2017-11-11 15:40 ` `thunk-let'? Eli Zaretskii 0 siblings, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-11 15:20 UTC (permalink / raw) To: Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > > > > I'm not sure I fully get what "internal" means in this context... > > > > > > It means not intended for use outside of Emacs internals. > > > > But no doubt `when', for example, is used outside of Emacs > > internals...? > > 'when' is in subr.el, not in subr-x.el. And its well documented. Ok, then `when-let*'. It's surely useful not only in Emacs internals. Lot's of packages in Gnu Elpa use it. It seems that the only more or less "internal" functions in subr-x are those named "internal-*", and these are of the kind "helper function" as exist in most libraries - so their names are misleading. For example, `internal--build-bindings' is clearly a helper for `if-let*', barely useful for anything else, including other let-style macro definitions. FWIW, my impression was always that subr-x.el was thought as an extension library for subr.el, and for me, it kind of looks like that. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-11 15:20 ` `thunk-let'? Michael Heerdegen @ 2017-11-11 15:40 ` Eli Zaretskii 0 siblings, 0 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-11 15:40 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: nicolas@petton.fr, monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Sat, 11 Nov 2017 16:20:58 +0100 > > Ok, then `when-let*'. It's surely useful not only in Emacs internals. > Lot's of packages in Gnu Elpa use it. Then it should be moved to subr.el or some other file. And it should be documented. > FWIW, my impression was always that subr-x.el was thought as an extension > library for subr.el, and for me, it kind of looks like that. If that is the case, it makes no sense not to document what's in there. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-11-09 17:20 ` `thunk-let'? Eli Zaretskii 2017-11-09 17:39 ` `thunk-let'? Clément Pit-Claudel 2017-11-09 18:14 ` `thunk-let'? Michael Heerdegen @ 2017-11-10 10:10 ` Nicolas Petton 2 siblings, 0 replies; 77+ messages in thread From: Nicolas Petton @ 2017-11-10 10:10 UTC (permalink / raw) To: Eli Zaretskii, Michael Heerdegen; +Cc: monnier, emacs-devel [-- Attachment #1: Type: text/plain, Size: 256 bytes --] Eli Zaretskii <eliz@gnu.org> writes: hi, > I'm not sure this should be in subr-x.el. That file is AFAIU for > stuff used _internally_ in Emacs, which this macro isn't. I think it complements other functions of thunk.el and belongs there. Cheers, Nico [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-11-08 18:04 ` Eli Zaretskii 2017-11-08 22:22 ` `thunk-let'? Michael Heerdegen @ 2017-11-09 14:34 ` Michael Heerdegen 2017-11-09 17:12 ` Eli Zaretskii 2017-11-09 15:19 ` Michael Heerdegen 2 siblings, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 14:34 UTC (permalink / raw) To: Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel [-- Attachment #1: Type: text/plain, Size: 321 bytes --] Eli Zaretskii <eliz@gnu.org> writes: > Given that "lazy evaluation" seems not to be described anywhere in the > ELisp manual, I think we cannot get away with "---" here, and will > have to add at least something to the manual. Ok, here is a first draft of how I could imagine an addition to the manual (as plain txt): [-- Attachment #2: lazy-let-doc.txt --] [-- Type: text/plain, Size: 2931 bytes --] Deferred and lazy evaluation Sometimes it is useful to delay the evaluation of an expression, for example if you want to avoid to perform a time-consuming calculation in the case that it turns out that the result is not needed in the future of the program. -- Macro: thunk-delay FORMS... Return a thunk for evaluating the FORMS. A thunk is a closure that evaluates the FORMS in the lexical environment present when `thunk-delay' has been called. -- Function: thunk-force THUNK Force a thunk to perform the evaluation of the FORMS specified to the `thunk-delay' that created the thunk. The result of the evaluation of the last form is returned. The THUNK also "remembers" that it has been forced: Any further calls of `thunk-force' on the same THUNK will just return the same result without evaluating the FORMS again. -- Macro: lazy-let (bindings...) forms... This macro is analogue to `let' but creates "lazy" variable bindings. Any binding has the form (SYMBOL VALUE-FORM). Unlike `let', the evaluation of any VALUE-FORM is deferred until the binding of the according SYMBOL is used for the first time when evaluating the FORMS. Any VALUE-FORM is evaluated maximally once. Example: (defun f (number) (lazy-let ((derived-number (progn (message "Calculating 1 plus 2 times %d" number) (1+ (* 2 number))))) (if (> number 10) derived-number number))) (f 5) ==> 5 (f 12) |--> "Calculating 1 plus 2 times 12" 25 Because of the special nature of lazily bound variables, it is an error to set them (e.g. with `setq'). -- Macro: lazy-let* (bindings...) forms... This is like `lazy-let' but any expression in BINDINGS is allowed to refer to preceding bindings in this `lazy-let*' form. Example: (lazy-let* ((x (prog2 (message "Calculating x...") (+ 1 1) (message "Finished calculating x"))) (y (prog2 (message "Calculating y...") (+ x 1) (message "Finished calculating y"))) (z (prog2 (message "Calculating z...") (+ y 1) (message "Finished calculating z"))) (a (prog2 (message "Calculating a...") (+ z 1) (message "Finished calculating a")))) (* z x)) |--> Calculating z... |--> Calculating y... |--> Calculating x... |--> Finished calculating x |--> Finished calculating y |--> Finished calculating z ==> 8 `lazy-let' and `lazy-let*' use thunks implicitly: their expansion creates helper symbols and binds them to thunks wrapping the binding expressions. All rerences to the original variables in the FORMS are then replaced by an expression that calls `thunk-force' on the according helper variable. So, any code using `lazy-let' or `lazy-let*' could be rewritten to use thunks, but in many cases using these macros results in nicer code than using thunks explicitly. [-- Attachment #3: Type: text/plain, Size: 114 bytes --] WDYT? I would definitely need a bit of help to turn this into texinfo with good Enlish. Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-11-09 14:34 ` [SUSPECTED SPAM] `thunk-let'? Michael Heerdegen @ 2017-11-09 17:12 ` Eli Zaretskii 0 siblings, 0 replies; 77+ messages in thread From: Eli Zaretskii @ 2017-11-09 17:12 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel > From: Michael Heerdegen <michael_heerdegen@web.de> > Cc: nicolas@petton.fr, monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Thu, 09 Nov 2017 15:34:30 +0100 > > Ok, here is a first draft of how I could imagine an addition to the > manual (as plain txt): Thanks. > -- Function: thunk-force THUNK > > Force a thunk to perform the evaluation of the FORMS specified to the > `thunk-delay' that created the thunk. This should have FORMS in lower-case and THUNK in upper-case > -- Macro: lazy-let (bindings...) forms... > > This macro is analogue to `let' but creates "lazy" variable bindings. ^^^^^^^^ "analogous" > Any binding has the form (SYMBOL VALUE-FORM). Unlike `let', the > evaluation of any VALUE-FORM is deferred until the binding of the > according SYMBOL is used for the first time when evaluating the FORMS. > Any VALUE-FORM is evaluated maximally once. ^^^^^^^^^ "at most" > WDYT? It's good, thank you. Let me know if you need specific help with Texinfo-izing this. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [SUSPECTED SPAM] Re: `thunk-let'? 2017-11-08 18:04 ` Eli Zaretskii 2017-11-08 22:22 ` `thunk-let'? Michael Heerdegen 2017-11-09 14:34 ` [SUSPECTED SPAM] `thunk-let'? Michael Heerdegen @ 2017-11-09 15:19 ` Michael Heerdegen 2 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-11-09 15:19 UTC (permalink / raw) To: Eli Zaretskii; +Cc: nicolas, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > > +(defmacro lazy-let (bindings &rest body) > > + "Like `let' but make delayed bindings. > > And this seems to use a different term for this. > > > +This is like `let' but any binding expression is not evaluated > > +before the variable is used for the first time. > > The "like let" part is a repetition of what was already said in the > first sentence, so there's no need to repeat it. Done. The updated cumulative patch is in the answer <87wp2zcwm2.fsf@web.de> to Stefan's response. Thanks for reviewing, Eli. Regards, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: `thunk-let'? 2017-10-08 20:12 `thunk-let'? Michael Heerdegen 2017-10-08 22:25 ` `thunk-let'? Michael Heerdegen 2017-10-09 3:10 ` `thunk-let'? Stefan Monnier @ 2017-10-09 8:00 ` Nicolas Petton 2017-12-08 20:38 ` A generalization of `thunk-let' (was: `thunk-let'?) Michael Heerdegen 3 siblings, 0 replies; 77+ messages in thread From: Nicolas Petton @ 2017-10-09 8:00 UTC (permalink / raw) To: Michael Heerdegen, Emacs Development; +Cc: Stefan Monnier [-- Attachment #1: Type: text/plain, Size: 264 bytes --] Michael Heerdegen <michael_heerdegen@web.de> writes: > Hi, Hi Michael, > An alternative name would be `delayed-let'. I think it would be very > convenient. I think `thunk-let' is a good name. > What do people think about this idea? I like it! Cheers, Nico [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 77+ messages in thread
* A generalization of `thunk-let' (was: `thunk-let'?) 2017-10-08 20:12 `thunk-let'? Michael Heerdegen ` (2 preceding siblings ...) 2017-10-09 8:00 ` `thunk-let'? Nicolas Petton @ 2017-12-08 20:38 ` Michael Heerdegen 2017-12-08 21:16 ` A generalization of `thunk-let' Stefan Monnier 2017-12-09 21:59 ` A generalization of `thunk-let' (was: `thunk-let'?) Richard Stallman 3 siblings, 2 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-12-08 20:38 UTC (permalink / raw) To: Emacs Development; +Cc: Nicolas Petton, Stefan Monnier Hello, I had an idea for something that turned out to generalize `thunk-let' - I called it `dep-let' ("dep" like "dependency") for now. I wonder if it's a useful or a silly idea. Say, you write a user interface, and it includes some toggle commands. When one of these toggle commands is called, the code will probably need to recompute some variables to adopt their bindings to the new set of options. So the code needs to introduce some functions to recompute the variables, and the developer must keep an eye on where these functions need to be called - it's something that makes code changes a bit painful. The idea is to make such dependencies explicit, but hide the recomputation stuff at the same time. Bindings are lazy, and are recomputed lazily when one of the dependencies changes. Here is a proof-of-concept implementation: #+begin_src emacs-lisp ;; -*- lexical-binding: t -*- (require 'cl-lib) (defun with-short-term-memory (function) "Wrap FUNCTION to cache the last arguments/result pair." (let ((cached nil)) (lambda (&rest args) (pcase cached (`(,(pred (equal args)) . ,result) result) (_ (cdr (setq cached (cons args (apply function args))))))))) (defmacro dep-let (bindings &rest body) "Make dependent bindings and evaluate BODY. This is similar to `let', but BINDINGS is a list of elements (VAR DEPS EXPRESSION). DEPS is a list of symbols declaring that the computation of this binding depends on the values of these symbols. All bindings are lazy. Whenever VAR is referenced and one of the DEPS has changed its value (modulo #'equal), the binding is silently recomputed." (declare (indent 1)) (cl-callf2 mapcar (pcase-lambda (`(,var ,deps ,binding)) (list (make-symbol (concat (symbol-name var) "-helper")) var deps binding)) bindings) `(let ,(mapcar (pcase-lambda (`(,helper-var ,_var ,deps ,binding)) `(,helper-var (with-short-term-memory (lambda ,deps ,binding)))) bindings) (cl-symbol-macrolet ,(mapcar (pcase-lambda (`(,helper-var ,var ,deps ,_binding)) `(,var (funcall ,helper-var ,@deps))) bindings) ,@body))) #+end_src You get `thunk-let' when you specify empty dependency lists. It may be useful to generalize the `equal' test, or to allow to specify something else as dependency (e.g. the result of a (fast) function call). Here are some very simple #+begin_src emacs-lisp ;;; Examples: (let ((a 1) (b 2)) (dep-let ((sum-of-a-and-b (a b) (progn (message "Calculating %d + %d" a b) (+ a b)))) (list sum-of-a-and-b (1+ sum-of-a-and-b) (* 2 sum-of-a-and-b) (progn (setq a 5) sum-of-a-and-b)))) |- Calculating 1 + 2 |- Calculating 5 + 2 => (3 4 6 7) ;; Dependencies can be recursive: (let ((a 1) (b 2) (c 3)) (dep-let ((a+b (a b) (+ a b)) (a+b+c (a+b c) (+ a+b c))) (list a+b a+b+c (progn (setq a 10) a+b+c)))) ==> (3 6 15) #+end_src Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-08 20:38 ` A generalization of `thunk-let' (was: `thunk-let'?) Michael Heerdegen @ 2017-12-08 21:16 ` Stefan Monnier 2017-12-09 10:33 ` Michael Heerdegen 2018-01-12 20:03 ` Michael Heerdegen 2017-12-09 21:59 ` A generalization of `thunk-let' (was: `thunk-let'?) Richard Stallman 1 sibling, 2 replies; 77+ messages in thread From: Stefan Monnier @ 2017-12-08 21:16 UTC (permalink / raw) To: emacs-devel > Say, you write a user interface, and it includes some toggle commands. > When one of these toggle commands is called, the code will probably need > to recompute some variables to adopt their bindings to the new set of > options. So the code needs to introduce some functions to recompute the > variables, and the developer must keep an eye on where these functions > need to be called - it's something that makes code changes a bit > painful. Another way to do that is to create a new kind of object which is like a spreadsheet-cell: it comes with an expression to compute it, and remembers its current value. Internally it also keeps track of the other spreadsheet-cells used during the computation of the current value (i.e. the dependencies are automatically tracked for you). You also get to choose whether to check the current value's validity lazily (like you do in your code), or to eagerly mark dependencies "dirty" when a cell is modified. > Here are some very simple > #+begin_src emacs-lisp > ;;; Examples: > (let ((a 1) > (b 2)) > (dep-let ((sum-of-a-and-b (a b) (progn (message "Calculating %d + %d" a b) (+ a b)))) Here you'd need to something like (let* ((a (make-sscell 1)) (b (make-sscell 2)) (sum-of-a-and-b (make-sscell (progn (message "Calculating %d + %d" a b) (+ (sscell-get a) (sscell-get b)))))) ...) tho you could provide a `sscell-let` to do the symbol-macrolet danse and let you write (sscell-let* ((a 1) (b 2) (sum-of-a-and-b (progn (message "Calculating %d + %d" a b) (+ a b)))) ...) > ;; Dependencies can be recursive: > (let ((a 1) > (b 2) > (c 3)) > (dep-let ((a+b (a b) (+ a b)) > (a+b+c (a+b c) (+ a+b c))) > (list a+b > a+b+c > (progn (setq a 10) a+b+c)))) I'm not sure I see the recursion, here. Are you talking about the fact that a+b+c depends on a but only mentions a+b in its dependencies? Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-08 21:16 ` A generalization of `thunk-let' Stefan Monnier @ 2017-12-09 10:33 ` Michael Heerdegen 2017-12-10 4:47 ` Stefan Monnier 2018-01-12 20:03 ` Michael Heerdegen 1 sibling, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-12-09 10:33 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > Another way to do that is to create a new kind of object which is like > a spreadsheet-cell: it comes with an expression to compute it, and > remembers its current value. Internally it also keeps track of the > other spreadsheet-cells used during the computation of the current > value (i.e. the dependencies are automatically tracked for you). That's a nice in-between layer (and it also offers the most important part - a package prefix!). Do you already have an implementation? > Here you'd need to something like > > (let* ((a (make-sscell 1)) > (b (make-sscell 2)) > (sum-of-a-and-b > (make-sscell > (progn (message "Calculating %d + %d" a b) > (+ (sscell-get a) (sscell-get b)))))) > ...) An advantage is that you don't have to make dependencies explicit (though, they are still a bit explicit due to the necessary usage of `sscell-get'). A disadvantage seems to be that the input variables (`a', `b') need to be declared as sscells, though they don't have dependencies themselves. > > ;; Dependencies can be recursive: > > > (let ((a 1) > > (b 2) > > (c 3)) > > (dep-let ((a+b (a b) (+ a b)) > > (a+b+c (a+b c) (+ a+b c))) > > (list a+b > > a+b+c > > (progn (setq a 10) a+b+c)))) > > I'm not sure I see the recursion, here. Are you talking about the fact > that a+b+c depends on a but only mentions a+b in its dependencies? Yes, exactly: I should have said "dependencies are resolved recursively". Regards, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-09 10:33 ` Michael Heerdegen @ 2017-12-10 4:47 ` Stefan Monnier 2017-12-10 5:34 ` John Wiegley 2017-12-12 14:41 ` Michael Heerdegen 0 siblings, 2 replies; 77+ messages in thread From: Stefan Monnier @ 2017-12-10 4:47 UTC (permalink / raw) To: Michael Heerdegen; +Cc: emacs-devel > That's a nice in-between layer (and it also offers the most important > part - a package prefix!). Do you already have an implementation? There wasn't enough space in the margin for it, sorry, Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-10 4:47 ` Stefan Monnier @ 2017-12-10 5:34 ` John Wiegley 2017-12-12 14:41 ` Michael Heerdegen 1 sibling, 0 replies; 77+ messages in thread From: John Wiegley @ 2017-12-10 5:34 UTC (permalink / raw) To: Stefan Monnier; +Cc: Michael Heerdegen, emacs-devel >>>>> "SM" == Stefan Monnier <monnier@iro.umontreal.ca> writes: SM> There wasn't enough space in the margin for it, sorry, Hah! :) Stefan's last macro. -- John Wiegley GPG fingerprint = 4710 CF98 AF9B 327B B80F http://newartisans.com 60E1 46C4 BD1A 7AC1 4BA2 ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-10 4:47 ` Stefan Monnier 2017-12-10 5:34 ` John Wiegley @ 2017-12-12 14:41 ` Michael Heerdegen 2017-12-13 13:52 ` Michael Heerdegen 1 sibling, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-12-12 14:41 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 760 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: > There wasn't enough space in the margin for it, sorry, Very funny, ehm - here is a proof of concept. It is not exactly what you suggested: you have to declare dependencies explicitly (I think this would even make code better readable). But dependencies do not need to be sscells - I made it so that dependencies can be arbitrary expressions (this includes variables as major case), and an equivalence predicate for testing can be optionally specified, defaulting to #'eq. You can make an sscell B depend on the value of sscell A by making B depend on the expression (sscell-get A). These sscell objects are a generalization of thunks as in thunk.el, and sscell-let is a generalization of thunk-let. [-- Attachment #2: sscell.el --] [-- Type: application/emacs-lisp, Size: 3521 bytes --] [-- Attachment #3: Type: text/plain, Size: 21 bytes --] Regards, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-12 14:41 ` Michael Heerdegen @ 2017-12-13 13:52 ` Michael Heerdegen 2017-12-13 14:09 ` Stefan Monnier 0 siblings, 1 reply; 77+ messages in thread From: Michael Heerdegen @ 2017-12-13 13:52 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel Hi, > here is a proof of concept. It is not exactly what you suggested: you > have to declare dependencies explicitly (I think this would even make > code better readable). But dependencies do not need to be sscells - I > made it so that dependencies can be arbitrary expressions (this includes > variables as major case), and an equivalence predicate for testing can > be optionally specified, defaulting to #'eq. > > You can make an sscell B depend on the value of sscell A by making B > depend on the expression (sscell-get A). > > These sscell objects are a generalization of thunks as in thunk.el, and > sscell-let is a generalization of thunk-let. Stefan, would you be ok with that approach? (or please speak before I do the final work and put it to Gnu Elpa...) Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-13 13:52 ` Michael Heerdegen @ 2017-12-13 14:09 ` Stefan Monnier 2017-12-13 14:37 ` Michael Heerdegen 0 siblings, 1 reply; 77+ messages in thread From: Stefan Monnier @ 2017-12-13 14:09 UTC (permalink / raw) To: emacs-devel > Stefan, would you be ok with that approach? (or please speak before I do > the final work and put it to Gnu Elpa...) I don't actually have an opinion: I can't remember needing something like that in Elisp, so I don't have any intuition for what would work better. Stefan ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-13 14:09 ` Stefan Monnier @ 2017-12-13 14:37 ` Michael Heerdegen 0 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-12-13 14:37 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > I don't actually have an opinion: I can't remember needing something > like that in Elisp, so I don't have any intuition for what would work > better. Ok, then I'll go with my approach. Thanks, Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-08 21:16 ` A generalization of `thunk-let' Stefan Monnier 2017-12-09 10:33 ` Michael Heerdegen @ 2018-01-12 20:03 ` Michael Heerdegen 1 sibling, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2018-01-12 20:03 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 964 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: > Another way to do that is to create a new kind of object which is like > a spreadsheet-cell: it comes with an expression to compute it, and > remembers its current value. Internally it also keeps track of the > other spreadsheet-cells used during the computation of the current > value (i.e. the dependencies are automatically tracked for you). > > You also get to choose whether to check the current value's validity > lazily (like you do in your code), or to eagerly mark dependencies > "dirty" when a cell is modified. I think I've got that working - see attachment (though I chose different function names). Of course we want a gv-setter for `sscell-get'! A surprising side effect of this is that, contrary to thunk-let, sscell-let bound variables suddenly get settable, e.g. #+begin_src emacs-lisp (sscell-let ((x () 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) ==> 80 #+end_src Michael. [-- Attachment #2: sscell.el --] [-- Type: application/emacs-lisp, Size: 8649 bytes --] ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' (was: `thunk-let'?) 2017-12-08 20:38 ` A generalization of `thunk-let' (was: `thunk-let'?) Michael Heerdegen 2017-12-08 21:16 ` A generalization of `thunk-let' Stefan Monnier @ 2017-12-09 21:59 ` Richard Stallman 2017-12-10 17:03 ` A generalization of `thunk-let' Michael Heerdegen 1 sibling, 1 reply; 77+ messages in thread From: Richard Stallman @ 2017-12-09 21:59 UTC (permalink / raw) To: Michael Heerdegen; +Cc: nicolas, monnier, emacs-devel [[[ To any NSA and FBI agents reading my email: please consider ]]] [[[ whether defending the US Constitution against all enemies, ]]] [[[ foreign or domestic, requires you to follow Snowden's example. ]]] > Say, you write a user interface, and it includes some toggle commands. > When one of these toggle commands is called, the code will probably need > to recompute some variables to adopt their bindings to the new set of > options. Why design this to work on let bindings rather than on top-level buffer-local bindings? Wouldn't the latter be most convenient for this sort of thing? -- Dr Richard Stallman President, Free Software Foundation (https://gnu.org, https://fsf.org) Internet Hall-of-Famer (https://internethalloffame.org) Skype: No way! See https://stallman.org/skype.html. ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: A generalization of `thunk-let' 2017-12-09 21:59 ` A generalization of `thunk-let' (was: `thunk-let'?) Richard Stallman @ 2017-12-10 17:03 ` Michael Heerdegen 0 siblings, 0 replies; 77+ messages in thread From: Michael Heerdegen @ 2017-12-10 17:03 UTC (permalink / raw) To: Richard Stallman; +Cc: nicolas, monnier, emacs-devel Richard Stallman <rms@gnu.org> writes: > Why design this to work on let bindings rather than on top-level > buffer-local bindings? Wouldn't the latter be most convenient for > this sort of thing? If I understand your question correctly: there is no such restriction - the code looks at the bindings of the as dependency declared variables - these don't have to be let-bindings. Michael. ^ permalink raw reply [flat|nested] 77+ messages in thread
end of thread, other threads:[~2018-01-12 20:03 UTC | newest] Thread overview: 77+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2017-10-08 20:12 `thunk-let'? Michael Heerdegen 2017-10-08 22:25 ` `thunk-let'? Michael Heerdegen 2017-10-09 3:10 ` `thunk-let'? Stefan Monnier 2017-10-09 11:40 ` `thunk-let'? Michael Heerdegen 2017-10-09 14:07 ` `thunk-let'? Michael Heerdegen 2017-10-09 14:27 ` `thunk-let'? Michael Heerdegen 2017-10-09 15:38 ` [SUSPECTED SPAM] `thunk-let'? Stefan Monnier 2017-11-08 17:22 ` Michael Heerdegen 2017-11-08 18:02 ` Stefan Monnier 2017-11-09 15:14 ` Michael Heerdegen 2017-11-09 18:39 ` `thunk-let'? Michael Heerdegen 2017-11-09 18:48 ` `thunk-let'? Stefan Monnier 2017-11-22 2:50 ` `thunk-let'? Michael Heerdegen 2017-11-22 3:43 ` `thunk-let'? Eli Zaretskii 2017-11-22 16:16 ` `thunk-let'? Eli Zaretskii 2017-11-22 19:25 ` `thunk-let'? Michael Heerdegen 2017-11-22 20:00 ` `thunk-let'? Eli Zaretskii 2017-11-23 2:59 ` `thunk-let'? Michael Heerdegen 2017-11-23 4:15 ` `thunk-let'? Michael Heerdegen 2017-11-23 16:34 ` `thunk-let'? Pip Cet 2017-11-23 23:41 ` `thunk-let'? Michael Heerdegen 2017-11-24 8:37 ` `thunk-let'? Eli Zaretskii 2017-11-24 8:51 ` `thunk-let'? Stefan Monnier 2017-11-24 9:16 ` `thunk-let'? Eli Zaretskii 2017-11-24 13:33 ` `thunk-let'? Stefan Monnier 2017-11-27 5:21 ` `thunk-let'? Michael Heerdegen 2017-11-27 13:34 ` `thunk-let'? Stefan Monnier 2017-11-27 15:44 ` `thunk-let'? Eli Zaretskii 2017-11-30 15:19 ` `thunk-let'? Michael Heerdegen 2017-11-24 8:36 ` `thunk-let'? Eli Zaretskii 2017-11-30 15:17 ` `thunk-let'? Michael Heerdegen 2017-11-30 16:06 ` `thunk-let'? Eli Zaretskii 2017-12-01 8:02 ` `thunk-let'? Michael Heerdegen 2017-11-23 16:04 ` `thunk-let'? Eli Zaretskii 2017-11-22 17:44 ` `thunk-let'? Gemini Lasswell 2017-11-22 18:04 ` `thunk-let'? Noam Postavsky 2017-11-22 18:31 ` `thunk-let'? Michael Heerdegen 2017-11-22 18:29 ` `thunk-let'? Michael Heerdegen 2017-11-22 19:54 ` `thunk-let'? Stefan Monnier 2017-11-22 22:47 ` `thunk-let'? Michael Heerdegen 2017-11-10 10:01 ` [SUSPECTED SPAM] `thunk-let'? Eli Zaretskii 2017-11-08 18:04 ` Eli Zaretskii 2017-11-08 22:22 ` `thunk-let'? Michael Heerdegen 2017-11-08 23:06 ` `thunk-let'? Drew Adams 2017-11-09 17:20 ` `thunk-let'? Eli Zaretskii 2017-11-09 17:39 ` `thunk-let'? Clément Pit-Claudel 2017-11-09 18:06 ` `thunk-let'? Michael Heerdegen 2017-11-09 21:05 ` `thunk-let'? Drew Adams 2017-11-09 23:07 ` Sandbox subr-x? (was: `thunk-let'?) Michael Heerdegen 2017-11-09 23:54 ` Drew Adams 2017-11-10 7:57 ` Eli Zaretskii 2017-11-09 21:48 ` `thunk-let'? Clément Pit-Claudel 2017-11-09 22:43 ` `thunk-let'? Michael Heerdegen 2017-11-10 7:48 ` `thunk-let'? Eli Zaretskii 2017-11-09 18:14 ` `thunk-let'? Michael Heerdegen 2017-11-09 20:26 ` `thunk-let'? Eli Zaretskii 2017-11-09 23:13 ` `thunk-let'? Michael Heerdegen 2017-11-10 7:58 ` `thunk-let'? Eli Zaretskii 2017-11-11 15:20 ` `thunk-let'? Michael Heerdegen 2017-11-11 15:40 ` `thunk-let'? Eli Zaretskii 2017-11-10 10:10 ` `thunk-let'? Nicolas Petton 2017-11-09 14:34 ` [SUSPECTED SPAM] `thunk-let'? Michael Heerdegen 2017-11-09 17:12 ` Eli Zaretskii 2017-11-09 15:19 ` Michael Heerdegen 2017-10-09 8:00 ` `thunk-let'? Nicolas Petton 2017-12-08 20:38 ` A generalization of `thunk-let' (was: `thunk-let'?) Michael Heerdegen 2017-12-08 21:16 ` A generalization of `thunk-let' Stefan Monnier 2017-12-09 10:33 ` Michael Heerdegen 2017-12-10 4:47 ` Stefan Monnier 2017-12-10 5:34 ` John Wiegley 2017-12-12 14:41 ` Michael Heerdegen 2017-12-13 13:52 ` Michael Heerdegen 2017-12-13 14:09 ` Stefan Monnier 2017-12-13 14:37 ` Michael Heerdegen 2018-01-12 20:03 ` Michael Heerdegen 2017-12-09 21:59 ` A generalization of `thunk-let' (was: `thunk-let'?) Richard Stallman 2017-12-10 17:03 ` A generalization of `thunk-let' Michael Heerdegen
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.