unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* `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-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

* 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 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: [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-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: [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: [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: `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 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 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: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 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

* 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

* 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: `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: 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: `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: 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 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: [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: `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: `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 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  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 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 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 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 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: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 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: `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  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-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  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-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-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-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-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

* 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' (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 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-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

* 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

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 public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).