aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Heerdegen2017-11-02 18:45:34 +0100
committerMichael Heerdegen2017-12-01 08:54:05 +0100
commitcc58d4de56e362f5e017d0607986b2962ee47fc1 (patch)
tree3b2427fc07973dd598424ed185b5a36bc0d69be9
parentef183144add2b92359a9ade2ec0b28681b26956b (diff)
downloademacs-cc58d4de56e362f5e017d0607986b2962ee47fc1.tar.gz
emacs-cc58d4de56e362f5e017d0607986b2962ee47fc1.zip
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.
-rw-r--r--doc/lispref/elisp.texi1
-rw-r--r--doc/lispref/eval.texi123
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/emacs-lisp/thunk.el59
-rw-r--r--test/lisp/emacs-lisp/thunk-tests.el50
5 files changed, 232 insertions, 5 deletions
diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index c7525945845..a271749e044 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -455,6 +455,7 @@ Evaluation
455 the program). 455 the program).
456* Backquote:: Easier construction of list structure. 456* Backquote:: Easier construction of list structure.
457* Eval:: How to invoke the Lisp interpreter explicitly. 457* Eval:: How to invoke the Lisp interpreter explicitly.
458* Deferred Eval:: Deferred and lazy evaluation of forms.
458 459
459Kinds of Forms 460Kinds of Forms
460 461
diff --git a/doc/lispref/eval.texi b/doc/lispref/eval.texi
index 064fca22ff5..74fefdb71bc 100644
--- a/doc/lispref/eval.texi
+++ b/doc/lispref/eval.texi
@@ -20,11 +20,12 @@ function @code{eval}.
20 20
21@ifnottex 21@ifnottex
22@menu 22@menu
23* Intro Eval:: Evaluation in the scheme of things. 23* Intro Eval:: Evaluation in the scheme of things.
24* Forms:: How various sorts of objects are evaluated. 24* Forms:: How various sorts of objects are evaluated.
25* Quoting:: Avoiding evaluation (to put constants in the program). 25* Quoting:: Avoiding evaluation (to put constants in the program).
26* Backquote:: Easier construction of list structure. 26* Backquote:: Easier construction of list structure.
27* Eval:: How to invoke the Lisp interpreter explicitly. 27* Eval:: How to invoke the Lisp interpreter explicitly.
28* Deferred Eval:: Deferred and lazy evaluation of forms.
28@end menu 29@end menu
29 30
30@node Intro Eval 31@node Intro Eval
@@ -877,3 +878,115 @@ particular elements, like this:
877@end group 878@end group
878@end example 879@end example
879@end defvar 880@end defvar
881
882@node Deferred Eval
883@section Deferred and Lazy Evaluation
884
885@cindex deferred evaluation
886@cindex lazy evaluation
887
888
889 Sometimes it is useful to delay the evaluation of an expression, for
890example if you want to avoid to perform a time-consuming calculation
891in the case that it turns out that the result is not needed in the
892future of the program. Therefore, the @file{thunk} library provides
893the following functions and macros:
894
895@cindex thunk
896@defmac thunk-delay forms@dots{}
897Return a @dfn{thunk} for evaluating the @var{forms}. A thunk is a
898closure (@pxref{Closures}) that inherits the lexical enviroment of the
899@code{thunk-delay} call. Using this macro requires
900@code{lexical-binding}.
901@end defmac
902
903@defun thunk-force thunk
904Force @var{thunk} to perform the evaluation of the forms specified in
905the @code{thunk-delay} that created the thunk. The result of the
906evaluation of the last form is returned. The @var{thunk} also
907``remembers'' that it has been forced: Any further calls of
908@code{thunk-force} with the same @var{thunk} will just return the same
909result without evaluating the forms again.
910@end defun
911
912@defmac thunk-let (bindings@dots{}) forms@dots{}
913This macro is analogous to @code{let} but creates ``lazy'' variable
914bindings. Any binding has the form @w{@code{(@var{symbol}
915@var{value-form})}}. Unlike @code{let}, the evaluation of any
916@var{value-form} is deferred until the binding of the according
917@var{symbol} is used for the first time when evaluating the
918@var{forms}. Any @var{value-form} is evaluated at most once. Using
919this macro requires @code{lexical-binding}.
920@end defmac
921
922Example:
923
924@example
925@group
926(defun f (number)
927 (thunk-let ((derived-number
928 (progn (message "Calculating 1 plus 2 times %d" number)
929 (1+ (* 2 number)))))
930 (if (> number 10)
931 derived-number
932 number)))
933@end group
934
935@group
936(f 5)
937@result{} 5
938@end group
939
940@group
941(f 12)
942@print{} Calculating 1 plus 2 times 12
943@result{} 25
944@end group
945
946@end example
947
948Because of the special nature of lazily bound variables, it is an error
949to set them (e.g.@: with @code{setq}).
950
951
952@defmac thunk-let* (bindings@dots{}) forms@dots{}
953This is like @code{thunk-let} but any expression in @var{bindings} is allowed
954to refer to preceding bindings in this @code{thunk-let*} form. Using
955this macro requires @code{lexical-binding}.
956@end defmac
957
958@example
959@group
960(thunk-let* ((x (prog2 (message "Calculating x...")
961 (+ 1 1)
962 (message "Finished calculating x")))
963 (y (prog2 (message "Calculating y...")
964 (+ x 1)
965 (message "Finished calculating y")))
966 (z (prog2 (message "Calculating z...")
967 (+ y 1)
968 (message "Finished calculating z")))
969 (a (prog2 (message "Calculating a...")
970 (+ z 1)
971 (message "Finished calculating a"))))
972 (* z x))
973
974@print{} Calculating z...
975@print{} Calculating y...
976@print{} Calculating x...
977@print{} Finished calculating x
978@print{} Finished calculating y
979@print{} Finished calculating z
980@result{} 8
981
982@end group
983@end example
984
985@code{thunk-let} and @code{thunk-let*} use thunks implicitly: their
986expansion creates helper symbols and binds them to thunks wrapping the
987binding expressions. All references to the original variables in the
988body @var{forms} are then replaced by an expression that calls
989@code{thunk-force} with the according helper variable as the argument.
990So, any code using @code{thunk-let} or @code{thunk-let*} could be
991rewritten to use thunks, but in many cases using these macros results
992in nicer code than using thunks explicitly.
diff --git a/etc/NEWS b/etc/NEWS
index c47ca42d277..6b3e7fc244b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -109,6 +109,10 @@ Snake and Pong are more playable on HiDPI displays.
109*** Completing filenames in the minibuffer via 'C-TAB' now uses the 109*** Completing filenames in the minibuffer via 'C-TAB' now uses the
110styles as configured by the variable 'completion-styles'. 110styles as configured by the variable 'completion-styles'.
111 111
112** New macros 'thunk-let' and 'thunk-let*'.
113These macros are analogue to 'let' and 'let*', but create bindings that
114are evaluated lazily.
115
112 116
113* New Modes and Packages in Emacs 27.1 117* New Modes and Packages in Emacs 27.1
114 118
diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el
index 371d10444b2..895fa86722d 100644
--- a/lisp/emacs-lisp/thunk.el
+++ b/lisp/emacs-lisp/thunk.el
@@ -41,6 +41,10 @@
41;; following: 41;; following:
42;; 42;;
43;; (thunk-force delayed) 43;; (thunk-force delayed)
44;;
45;; This file also defines macros `thunk-let' and `thunk-let*' that are
46;; analogous to `let' and `let*' but provide lazy evaluation of
47;; bindings by using thunks implicitly (i.e. in the expansion).
44 48
45;;; Code: 49;;; Code:
46 50
@@ -71,5 +75,60 @@ with the same DELAYED argument."
71 "Return non-nil if DELAYED has been evaluated." 75 "Return non-nil if DELAYED has been evaluated."
72 (funcall delayed t)) 76 (funcall delayed t))
73 77
78(defmacro thunk-let (bindings &rest body)
79 "Like `let' but create lazy bindings.
80
81BINDINGS is a list of elements of the form (SYMBOL EXPRESSION).
82Any binding EXPRESSION is not evaluated before the variable
83SYMBOL is used for the first time when evaluating the BODY.
84
85It is not allowed to set `thunk-let' or `thunk-let*' bound
86variables.
87
88Using `thunk-let' and `thunk-let*' requires `lexical-binding'."
89 (declare (indent 1) (debug let))
90 (cl-callf2 mapcar
91 (lambda (binding)
92 (pcase binding
93 (`(,(pred symbolp) ,_) binding)
94 (_ (signal 'error (cons "Bad binding in thunk-let"
95 (list binding))))))
96 bindings)
97 (cl-callf2 mapcar
98 (pcase-lambda (`(,var ,binding))
99 (list (make-symbol (concat (symbol-name var) "-thunk"))
100 var binding))
101 bindings)
102 `(let ,(mapcar
103 (pcase-lambda (`(,thunk-var ,_var ,binding))
104 `(,thunk-var (thunk-delay ,binding)))
105 bindings)
106 (cl-symbol-macrolet
107 ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding))
108 `(,var (thunk-force ,thunk-var)))
109 bindings)
110 ,@body)))
111
112(defmacro thunk-let* (bindings &rest body)
113 "Like `let*' but create lazy bindings.
114
115BINDINGS is a list of elements of the form (SYMBOL EXPRESSION).
116Any binding EXPRESSION is not evaluated before the variable
117SYMBOL is used for the first time when evaluating the BODY.
118
119It is not allowed to set `thunk-let' or `thunk-let*' bound
120variables.
121
122Using `thunk-let' and `thunk-let*' requires `lexical-binding'."
123 (declare (indent 1) (debug let))
124 (cl-reduce
125 (lambda (expr binding) `(thunk-let (,binding) ,expr))
126 (nreverse bindings)
127 :initial-value (macroexp-progn body)))
128
129;; (defalias 'lazy-let #'thunk-let)
130;; (defalias 'lazy-let* #'thunk-let*)
131
132
74(provide 'thunk) 133(provide 'thunk)
75;;; thunk.el ends here 134;;; thunk.el ends here
diff --git a/test/lisp/emacs-lisp/thunk-tests.el b/test/lisp/emacs-lisp/thunk-tests.el
index 973a14b8180..a63ce289e8a 100644
--- a/test/lisp/emacs-lisp/thunk-tests.el
+++ b/test/lisp/emacs-lisp/thunk-tests.el
@@ -51,5 +51,55 @@
51 (thunk-force thunk) 51 (thunk-force thunk)
52 (should (= x 1)))) 52 (should (= x 1))))
53 53
54
55
56;; thunk-let tests
57
58(ert-deftest thunk-let-basic-test ()
59 "Test whether bindings are established."
60 (should (equal (thunk-let ((x 1) (y 2)) (+ x y)) 3)))
61
62(ert-deftest thunk-let*-basic-test ()
63 "Test whether bindings are established."
64 (should (equal (thunk-let* ((x 1) (y (+ 1 x))) (+ x y)) 3)))
65
66(ert-deftest thunk-let-bound-vars-cant-be-set-test ()
67 "Test whether setting a `thunk-let' bound variable fails."
68 (should-error
69 (eval '(thunk-let ((x 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) t)))
70
71(ert-deftest thunk-let-laziness-test ()
72 "Test laziness of `thunk-let'."
73 (should
74 (equal (let ((x-evalled nil)
75 (y-evalled nil))
76 (thunk-let ((x (progn (setq x-evalled t) (+ 1 2)))
77 (y (progn (setq y-evalled t) (+ 3 4))))
78 (let ((evalled-y y))
79 (list x-evalled y-evalled evalled-y))))
80 (list nil t 7))))
81
82(ert-deftest thunk-let*-laziness-test ()
83 "Test laziness of `thunk-let*'."
84 (should
85 (equal (let ((x-evalled nil)
86 (y-evalled nil)
87 (z-evalled nil)
88 (a-evalled nil))
89 (thunk-let* ((x (progn (setq x-evalled t) (+ 1 1)))
90 (y (progn (setq y-evalled t) (+ x 1)))
91 (z (progn (setq z-evalled t) (+ y 1)))
92 (a (progn (setq a-evalled t) (+ z 1))))
93 (let ((evalled-z z))
94 (list x-evalled y-evalled z-evalled a-evalled evalled-z))))
95 (list t t t nil 4))))
96
97(ert-deftest thunk-let-bad-binding-test ()
98 "Test whether a bad binding causes an error when expanding."
99 (should-error (macroexpand '(thunk-let ((x 1 1)) x)))
100 (should-error (macroexpand '(thunk-let (27) x)))
101 (should-error (macroexpand '(thunk-let x x))))
102
103
54(provide 'thunk-tests) 104(provide 'thunk-tests)
55;;; thunk-tests.el ends here 105;;; thunk-tests.el ends here