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, 30 Nov 2017 16:17:30 +0100 Message-ID: <87609rokxh.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> <873755hbpr.fsf@web.de> <83shd49ipn.fsf@gnu.org> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1512055102 29776 195.159.176.226 (30 Nov 2017 15:18:22 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Thu, 30 Nov 2017 15:18:22 +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 30 16:18:18 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 1eKQbR-0007Kn-2T for ged-emacs-devel@m.gmane.org; Thu, 30 Nov 2017 16:18:17 +0100 Original-Received: from localhost ([::1]:48815 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eKQbW-0006pT-Gm for ged-emacs-devel@m.gmane.org; Thu, 30 Nov 2017 10:18:22 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:39674) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eKQar-0006pA-Hq for emacs-devel@gnu.org; Thu, 30 Nov 2017 10:17:43 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1eKQak-0004SV-CT for emacs-devel@gnu.org; Thu, 30 Nov 2017 10:17:41 -0500 Original-Received: from mout.web.de ([217.72.192.78]:58410) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1eKQaj-0004RS-W4; Thu, 30 Nov 2017 10:17:34 -0500 Original-Received: from drachen.dragon ([188.106.175.242]) by smtp.web.de (mrweb101 [213.165.67.124]) with ESMTPSA (Nemesis) id 0MOAjQ-1ePwuE2ktQ-005bFY; Thu, 30 Nov 2017 16:17:30 +0100 In-Reply-To: <83shd49ipn.fsf@gnu.org> (Eli Zaretskii's message of "Fri, 24 Nov 2017 10:36:20 +0200") X-Provags-ID: V03:K0:LJaD7fOXRZlaiF+5i3mSY1w5q/xfZlQlyFjeyc2IAxCncieuhDJ 9S9fIx46uNjkI29okyV7i7F33trqBrBd5BM83fonHfWccchjGopN57RKEM48dU41z3g0vzO IDoSqxJQP5xcPK2MgQe+oN93qVVAQgiPHiQ5RzeSexdSnvsbe6vZ2CJibDB/kxUCTv9jTa1 Sq96PJbRElYfiCReGFrUA== X-UI-Out-Filterresults: notjunk:1;V01:K0:HzUxpl0G1qo=:K+UU5uuBurki7B8ENjUUM+ 05Ag1ikOna4nYOlA2vucNC1z7ozAvrFZK7ZZhniaPWtXHNza3wv0cs4kUyPX2gwlTPEB/U1tm WA2xmkuna8ZH20L6TFHezV6G09sNy2ciPISVzsx8ITJz8OtMETLL7qiCNHPnM7wQmG5uWEE36 YuJlhs0pX7p5Nh8VyucyPreGoJyI7Hm0GF0uclM+3au0IE9QNvp1LYE6VYi1o8zLaHGjbTiKJ ov7ODNmck1r2pXx79chnAhe4YRI/NKDvHzAWHZZjyCucS40t5sEAgDDB9WmyLhoQloiB4trLF JUlDZCvwjuY8vRwpYSKpN0xCfI+ivTh1BQJUqZwf7pwAS9Yd7kEQtO4x1H9ciK5c/6XXaZkwB B9y1nXAUGPHVGta1vbxoPZbyWcOm0TD/3Es973ZURlC7JRi600SR5/IhMfUQfB3SgLcMsHwMr aYSnQE9HKpB/EPC25hgMC9SANvhQI9yBP24DorYgCX2O8kOOLBYL2Mx1a04Q+4FY4aB3c9+36 j1u0jvwJ/EciGRbvLD2SysxpS2t/Usku+n/wafMwCdVt+2VifsooEJ2RHio0GzACqZgmd5r5l EH9ME2zHGknYEowI3FXO3F6OaWllxVmr0wvFcPJ1G3t0cwNL579jD7t8tDG47jVMMIYslIe6m wiOkBwsSwOPhIVu9adDfHefOasRGVKX2KkLWDe3Uont47w4DN5Ta0COrFkcKuBsvkZ23mq2TM RtS4momkC8Pkihqi6CCOw/wBqSJp088m0vqS7zZVNZAmgz+vQUCcKC7LKgnO1k5IEtUcvMgD X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 217.72.192.78 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:220547 Archived-At: --=-=-= Content-Type: text/plain 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. --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-Add-macros-thunk-let-and-thunk-let.patch >From 66723e903caff1067a356cbc71a55aa7825579ab 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 | 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. + * 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)))) + + +;; 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 --=-=-=--