From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Michael Heerdegen Newsgroups: gmane.emacs.devel Subject: Re: `thunk-let'? Date: Thu, 23 Nov 2017 05:15:44 +0100 Message-ID: <873755hbpr.fsf@web.de> References: <87infp9z6j.fsf@web.de> <87zi90eehg.fsf@web.de> <87o9ocd6s4.fsf@web.de> <87wp2zcwm2.fsf@web.de> <87mv3vwb2c.fsf_-_@web.de> <87h8tnowl4.fsf@web.de> <83vai3asgt.fsf@gnu.org> <83mv3eb85m.fsf@gnu.org> <87a7zdbsyf.fsf@web.de> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1511410604 6208 195.159.176.226 (23 Nov 2017 04:16:44 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Thu, 23 Nov 2017 04:16:44 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.0.50 (gnu/linux) Cc: emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Thu Nov 23 05:16:36 2017 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eHiwA-0000ls-Sy for ged-emacs-devel@m.gmane.org; Thu, 23 Nov 2017 05:16:31 +0100 Original-Received: from localhost ([::1]:42448 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eHiwF-00064f-Fo for ged-emacs-devel@m.gmane.org; Wed, 22 Nov 2017 23:16:38 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:55865) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eHiva-00064Y-5g for emacs-devel@gnu.org; Wed, 22 Nov 2017 23:15:57 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1eHivX-0006Rq-9t for emacs-devel@gnu.org; Wed, 22 Nov 2017 23:15:54 -0500 Original-Received: from mout.web.de ([212.227.17.11]:60441) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1eHivT-0006Od-4Z; Wed, 22 Nov 2017 23:15:47 -0500 Original-Received: from drachen.dragon ([88.66.201.17]) by smtp.web.de (mrweb101 [213.165.67.124]) with ESMTPSA (Nemesis) id 0MgfJz-1eTUsZ3cil-00NxA1; Thu, 23 Nov 2017 05:15:45 +0100 In-Reply-To: <87a7zdbsyf.fsf@web.de> (Michael Heerdegen's message of "Thu, 23 Nov 2017 03:59:52 +0100") X-Provags-ID: V03:K0:3d9fvp53u4sMktky3N3kDaHJ5OeyQEIeuLMqbo4VSr5flsx4Xck 7YOGYQ2xlVL/iX6alDBqGE6/dhcMdgZpfbIeDPdehwPUC8+G2GmXjAOnQQFH8x0IYAresBf hVQtaBD7rWj1Ch6ziU079qcNR2/uW+efmanA+ge8ER1VVbeOiCR+ZlC4Is3JqYseDxOOuhf s42WHwEqUiP2dJcTjrJ0g== X-UI-Out-Filterresults: notjunk:1;V01:K0:Z4SvVobe/bk=:Q5sylvxBZ/+ge9XD0UsaGt vNaGmOhOmNw0Wz7fosio24pjNZuhCUpFSC3MvniPmV8GpmvHBqQ8J6XwJaRd8Q9Z/oFoGO1Jz RYvX3/nXoz2hEcpFr3RMqYhZ3VNuB/ZUPXtCF9IoB2kShdrUevafwk3MWlYVyr3fbYBp2F/rH EKAcA+GYgHuyvm3IpesQ+ztE8Pb5gZm8v8iwBi4z1mABxegAuWaOTMPx+IldXI0NOgbqJm7CK Cp4aP4w7g4TuEXTQmuQ2Wbj48fp/i204dlkXR29OkfQTh43dz3PNl+Y1YRI5XhJXERwBxMgv0 gkFOChfy7DOQu30dLcFvNNcNEsejyb/F3T5Nk0wpf96WcNkdk4io1sRaAOIn154uXs84Oo/nZ uiTrCleAzX1RMLStETnillMOwJcREiIdcMgGzygTSROKf2/o6g3EJ3k6h9LVUK9aWJ8HRf+QT P1EZC1LgCYTIQhOjB5LKCNRAHwb6XERQllOIQCONic+upbHHb1qYLZtELjlWo9lUTdQp7wcXU 3rV6Ou1rHHPRE2vUfRjU5mKkiNCBqoMvm3lGlmmC03DOKSl2FPp8BdtfUtUGPd9V1Y3YR3Duf tdt6kiwlGVQRMpEpdyU8fFa0/LOYL5r9Kvzn0TzMFrqpLX1kfz9tjoZazoZGac6gv6nU7TxXo RHEXvB/WRnlfg+jf9f+c6lA7mVKhvSvRwMf+uuNDkQb8YiMdxaOgqU9LrV4m+Z7Qs92Bnxqxq lCeyf43gIkZBdSEA2Cr5HOzTn1CBY7vR0Ubz1aZtp0p2plZw5RtIJ0Bz4M1xBAtoVleXZRGL X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 212.227.17.11 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:220384 Archived-At: --=-=-= Content-Type: text/plain Michael Heerdegen 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. --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-Add-macros-thunk-let-and-thunk-let.patch >From 6d7028defb30b403505a72ecf68ac52fe478d562 Mon Sep 17 00:00:00 2001 From: Michael Heerdegen 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. + * 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)))) + + +;; 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 --=-=-= Content-Type: text/plain Thanks, Michael. --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-Add-macros-thunk-let-and-thunk-let.patch >From 6d7028defb30b403505a72ecf68ac52fe478d562 Mon Sep 17 00:00:00 2001 From: Michael Heerdegen 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. + * 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)))) + + +;; 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 --=-=-=--