mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-12-24 10:38:38 +00:00
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.
This commit is contained in:
parent
ef183144ad
commit
cc58d4de56
@ -455,6 +455,7 @@ Evaluation
|
||||
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
|
||||
|
||||
|
@ -20,11 +20,12 @@ function @code{eval}.
|
||||
|
||||
@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 @@ particular elements, like this:
|
||||
@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.
|
||||
|
4
etc/NEWS
4
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
|
||||
|
||||
|
@ -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 @@ with the same DELAYED argument."
|
||||
"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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user