unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Michael Heerdegen <michael_heerdegen@web.de>
To: Eli Zaretskii <eliz@gnu.org>
Cc: emacs-devel@gnu.org
Subject: Re: `thunk-let'?
Date: Thu, 30 Nov 2017 16:17:30 +0100	[thread overview]
Message-ID: <87609rokxh.fsf@web.de> (raw)
In-Reply-To: <83shd49ipn.fsf@gnu.org> (Eli Zaretskii's message of "Fri, 24 Nov 2017 10:36:20 +0200")

[-- 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


  reply	other threads:[~2017-11-30 15:17 UTC|newest]

Thread overview: 77+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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                             ` Michael Heerdegen [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87609rokxh.fsf@web.de \
    --to=michael_heerdegen@web.de \
    --cc=eliz@gnu.org \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).