2

Given a set of lexical variables, is it feasible to rebind a subset of them depending on circumstances at runtime. My first idea was to use #'set something like:

(let (A B C D E)
  (declare (ignorable A B C D E))
  (mapc #'set '(b e) (list 1 2))  ;(list 1 2) stands for a function call to get values
  ...)

but this only works for special variables (and is depreciated). I have come up with a solution that does work (by building a setf expression), but am reluctant to show it as it is so complex and inefficient. Is there a straightforward solution?

(ps: I appreciate this problem is perverse, but presently don't see a way to refactor around it.)

davypough
  • 1,847
  • 11
  • 21

2 Answers2

2

Maybe something like this: a CASE maps from a sym to the setter code.

(let (a b c d e)
  (flet ((set-var (sym value)
           (macrolet ((sym-case (&rest syms)
                        `(case sym
                           ,@(mapcar (lambda (sym)
                                       `(,sym (setf ,sym value)))
                                     syms))))
             (sym-case a b c d e))))
    (mapc #'set-var '(b e) '(1 2))
    (list a b c d e)))

Result: (NIL 1 NIL NIL 2)

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Great integration of ideas! The one implementation problem I have is with the macro call `(sym-case a b c d e)`. In the interest of clarity, I inadvertantly oversimplified. The relevant lexical symbols are actually stored in a list (which changes on each invocation), so I build the initial let statement with ``(let ,lex-syms ...)`. Not sure how to amend your code to allow evaluation of the arguments. Can this be adapted to use a list of symbols? – davypough Feb 02 '23 at 21:40
  • On second thought, it looks like a simple backquote of the let will allow the insertion of a variable. Thanks again. – davypough Feb 03 '23 at 16:19
2

This is another use for a macro like let/names I provided in another answer. Here is a corrected version of it (you will need Tim Bradshaw's org.tfeb.hax.utilities package for parse-simple-body but this is just used to put declarations in the right place):

(defmacro let/names (bindings &body decls/forms)
  (multiple-value-bind (decls forms) (parse-simple-body decls/forms)
    (let ((<map> (make-symbol "MAP")))
      `(let ,bindings
         (let ((,<map> (list ,@(mapcar (lambda (b)
                                         (let ((n (etypecase b
                                                    (symbol b)
                                                    (cons (car b)))))
                                           `(cons ',n
                                                   (lambda (&optional (v nil vp))
                                                     (if vp
                                                         (setf ,n v)
                                                       ,n)))))
                                       bindings))))
           ,@decls
           (flet ((valof (s)
                    (let ((m (assoc s ,<map>)))
                      (if m
                          (values (funcall (cdr m)) t)
                        (values nil nil))))
                  ((setf valof) (n s)
                    (let ((m (assoc s ,<map>)))
                      (unless m
                        (error "~S unbound" s))
                      (funcall (cdr m) n))))
             ,@forms))))))

Now, for instance, you could write this:

(defun reset-variable (var val)
  (let/names ((a 1) (b 2) (c 3))
    (unless (nth-value 1 (valof var))
      (error "~S not bound here" var))
    (setf (valof var) val)
    (values a b c)))

And now:

> (reset-variable 'a 15)
15
2
3

> (reset-variable 'd 14)

Error: d not bound here
  1 (abort) Return to top loop level 0.

As you can see from the macro, all it does is build a secret table from the names of the variables to functions which access their bindings, and then defines a local valof / (setf valof) function appropriately.

ignis volens
  • 7,040
  • 2
  • 12