4

Whilst going over let over lambda I happened across

(defmacro! dlambda (&rest ds)
  `(lambda (&rest ,g!args)
     (case (car ,g!args)
       ,@(mapcar
           (lambda (d)
             `(,(if (eq t (car d))
                  t
                  (list (car d)))
               (apply (lambda ,@(cdr d))
                      ,(if (eq t (car d))
                         g!args
                         `(cdr ,g!args)))))
           ds))))

Which they subsequently invoke like so:

(setf (symbol-function 'count-test)
    (let ((count 0))
      (dlambda
        (:inc () (incf count))
        (:dec () (decf count)))))

Is there a construct like flet / labels / let that I can bind the resulting closure to, so as to avoid using funcall or the setf symbol-function in a global manner? So I can do something like:

(with-closures ((counter (let ((count 0))
                          (dlambda
                           (:inc () (incf count))
                           (:dec () (decf count))))))
              (counter :incf))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
cheshirecatalyst
  • 379
  • 1
  • 5
  • 14

1 Answers1

6

You could write a macro:

(defmacro as-functions ((&rest names) &body body)
  (assert (every #'symbolp names) () "Names must be symbols")
  (let ((args (copy-symbol :args)))
    `(flet
         ,(mapcar (lambda (n) `(,n (&rest ,args) (apply ,n ,args))) names)
       ,@body)))

For each symbol s in names, bind this symbol in the function namespace to the function currently bound by this symbol in the variable namespace. This can shadow any functions already named s in current lexical scope, but since it is done explicitly, the programmer shouldn't be caught by surprise. For example:

(let ((a (lambda (u) (+ 3 u)))
      (b (lambda (u) (* 2 u))))
  (as-functions (a b)
    (a (b 3))))

... macroexpands as:

(LET ((A (LAMBDA (U) (+ 3 U))) (B (LAMBDA (U) (* 2 U))))
  (FLET ((A (&REST #:ARGS)
           (APPLY A #:ARGS))
         (B (&REST #:ARGS)
           (APPLY B #:ARGS)))
    (A (B 3))))

... and evaluates to 9.

Contrary to a binding construct, this can be used with function arguments:

(defun compose (f g)
  (as-functions (f g)
    (lambda (x) (f (g x)))))

Provide bindings too

Based from a comment from jkiiski, here is a modified version which accepts (name fun) bindings in addition to single symbols. This looks like FLET, except that functions can be computed at runtime.

(defmacro as-functions ((&rest names) &body body)
  (let ((args (copy-symbol :args)))
    `(flet
         ,(mapcar
           (lambda (name)
             (etypecase name
               (symbol `(,name (&rest ,args) (apply ,name ,args)))
               (cons (destructuring-bind (name fun) name
                       `(,name (&rest ,args) (apply ,fun ,args))))))
           names)
       ,@body)))

And so:

(defun thing (f g h)
  (as-functions (f (k (compose g h)))
    (f (k 3))))

Edit: I remembered having read such a macro previously, using MACROLET: see this reply from Erik Naggum in comp.lang.lisp's Re: Why is Scheme not a Lisp?

coredump
  • 37,664
  • 5
  • 43
  • 77
  • 2
    If we think a moment about efficiency, consing rest arglists, applying functions on list arguments and added non-inlined functions may need more work for 'production' code. See also dynamic-extent declarations... – Rainer Joswig Oct 25 '16 at 20:16