diff options
| author | Michael Heerdegen | 2017-11-02 18:45:34 +0100 |
|---|---|---|
| committer | Michael Heerdegen | 2017-12-01 08:54:05 +0100 |
| commit | cc58d4de56e362f5e017d0607986b2962ee47fc1 (patch) | |
| tree | 3b2427fc07973dd598424ed185b5a36bc0d69be9 | |
| parent | ef183144add2b92359a9ade2ec0b28681b26956b (diff) | |
| download | emacs-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.texi | 1 | ||||
| -rw-r--r-- | doc/lispref/eval.texi | 123 | ||||
| -rw-r--r-- | etc/NEWS | 4 | ||||
| -rw-r--r-- | lisp/emacs-lisp/thunk.el | 59 | ||||
| -rw-r--r-- | 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 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 | ||
| 459 | Kinds of Forms | 460 | Kinds 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 | ||
| 890 | example if you want to avoid to perform a time-consuming calculation | ||
| 891 | in the case that it turns out that the result is not needed in the | ||
| 892 | future of the program. Therefore, the @file{thunk} library provides | ||
| 893 | the following functions and macros: | ||
| 894 | |||
| 895 | @cindex thunk | ||
| 896 | @defmac thunk-delay forms@dots{} | ||
| 897 | Return a @dfn{thunk} for evaluating the @var{forms}. A thunk is a | ||
| 898 | closure (@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 | ||
| 904 | Force @var{thunk} to perform the evaluation of the forms specified in | ||
| 905 | the @code{thunk-delay} that created the thunk. The result of the | ||
| 906 | evaluation 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 | ||
| 909 | result without evaluating the forms again. | ||
| 910 | @end defun | ||
| 911 | |||
| 912 | @defmac thunk-let (bindings@dots{}) forms@dots{} | ||
| 913 | This macro is analogous to @code{let} but creates ``lazy'' variable | ||
| 914 | bindings. 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 | ||
| 919 | this macro requires @code{lexical-binding}. | ||
| 920 | @end defmac | ||
| 921 | |||
| 922 | Example: | ||
| 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 | |||
| 948 | Because of the special nature of lazily bound variables, it is an error | ||
| 949 | to set them (e.g.@: with @code{setq}). | ||
| 950 | |||
| 951 | |||
| 952 | @defmac thunk-let* (bindings@dots{}) forms@dots{} | ||
| 953 | This is like @code{thunk-let} but any expression in @var{bindings} is allowed | ||
| 954 | to refer to preceding bindings in this @code{thunk-let*} form. Using | ||
| 955 | this 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 | ||
| 986 | expansion creates helper symbols and binds them to thunks wrapping the | ||
| 987 | binding expressions. All references to the original variables in the | ||
| 988 | body @var{forms} are then replaced by an expression that calls | ||
| 989 | @code{thunk-force} with the according helper variable as the argument. | ||
| 990 | So, any code using @code{thunk-let} or @code{thunk-let*} could be | ||
| 991 | rewritten to use thunks, but in many cases using these macros results | ||
| 992 | in nicer code than using thunks explicitly. | ||
| @@ -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 |
| 110 | styles as configured by the variable 'completion-styles'. | 110 | styles as configured by the variable 'completion-styles'. |
| 111 | 111 | ||
| 112 | ** New macros 'thunk-let' and 'thunk-let*'. | ||
| 113 | These macros are analogue to 'let' and 'let*', but create bindings that | ||
| 114 | are 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 | |||
| 81 | BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). | ||
| 82 | Any binding EXPRESSION is not evaluated before the variable | ||
| 83 | SYMBOL is used for the first time when evaluating the BODY. | ||
| 84 | |||
| 85 | It is not allowed to set `thunk-let' or `thunk-let*' bound | ||
| 86 | variables. | ||
| 87 | |||
| 88 | Using `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 | |||
| 115 | BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). | ||
| 116 | Any binding EXPRESSION is not evaluated before the variable | ||
| 117 | SYMBOL is used for the first time when evaluating the BODY. | ||
| 118 | |||
| 119 | It is not allowed to set `thunk-let' or `thunk-let*' bound | ||
| 120 | variables. | ||
| 121 | |||
| 122 | Using `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 |