emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

A generalization of `thunk-let' (was: `thunk-let'?)


From: Michael Heerdegen
Subject: A generalization of `thunk-let' (was: `thunk-let'?)
Date: Fri, 08 Dec 2017 21:38:10 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.0.50 (gnu/linux)

Hello,

I had an idea for something that turned out to generalize `thunk-let' -
I called it `dep-let' ("dep" like "dependency") for now.  I wonder if
it's a useful or a silly idea.

Say, you write a user interface, and it includes some toggle commands.
When one of these toggle commands is called, the code will probably need
to recompute some variables to adopt their bindings to the new set of
options.  So the code needs to introduce some functions to recompute the
variables, and the developer must keep an eye on where these functions
need to be called - it's something that makes code changes a bit
painful.

The idea is to make such dependencies explicit, but hide the
recomputation stuff at the same time.  Bindings are lazy, and are
recomputed lazily when one of the dependencies changes.

Here is a proof-of-concept implementation:

#+begin_src emacs-lisp
;; -*- lexical-binding: t -*-

(require 'cl-lib)

(defun with-short-term-memory (function)
  "Wrap FUNCTION to cache the last arguments/result pair."
  (let ((cached nil))
    (lambda (&rest args)
      (pcase cached
        (`(,(pred (equal args)) . ,result) result)
        (_ (cdr (setq cached (cons args (apply function args)))))))))

(defmacro dep-let (bindings &rest body)
  "Make dependent bindings and evaluate BODY.

This is similar to `let', but BINDINGS is a list of elements
(VAR DEPS EXPRESSION).  DEPS is a list of symbols declaring that the
computation of this binding depends on the values of these symbols.

All bindings are lazy.  Whenever VAR is referenced and one of the DEPS has
changed its value (modulo #'equal), the binding is silently
recomputed."
  (declare (indent 1))
  (cl-callf2 mapcar
      (pcase-lambda (`(,var ,deps ,binding))
        (list (make-symbol (concat (symbol-name var) "-helper"))
              var deps binding))
      bindings)
  `(let ,(mapcar
          (pcase-lambda (`(,helper-var ,_var ,deps ,binding))
            `(,helper-var (with-short-term-memory (lambda ,deps ,binding))))
          bindings)
     (cl-symbol-macrolet
         ,(mapcar (pcase-lambda (`(,helper-var ,var ,deps ,_binding))
                    `(,var (funcall ,helper-var ,@deps)))
                  bindings)
       ,@body)))
#+end_src

You get `thunk-let' when you specify empty dependency lists.

It may be useful to generalize the `equal' test, or to allow to specify
something else as dependency (e.g. the result of a (fast) function
call).

Here are some very simple

#+begin_src emacs-lisp
;;; Examples:

(let ((a 1)
      (b 2))
  (dep-let ((sum-of-a-and-b (a b) (progn (message "Calculating %d + %d" a b) (+ 
a b))))
    (list sum-of-a-and-b
          (1+ sum-of-a-and-b)
          (* 2 sum-of-a-and-b)
          (progn (setq a 5)
                 sum-of-a-and-b))))

|- Calculating 1 + 2
|- Calculating 5 + 2
=> (3 4 6 7)


;; Dependencies can be recursive:

(let ((a 1)
      (b 2)
      (c 3))
  (dep-let ((a+b   (a b)   (+ a b))
            (a+b+c (a+b c) (+ a+b c)))
    (list a+b
          a+b+c
          (progn (setq a 10) a+b+c))))

==> (3 6 15)
#+end_src


Thanks,

Michael.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]