A Sketch of DYNAMIC-FLET
An Exercise in Extension
Chatting with a friend about dyanmically rebinding functions in Emacs, I wondered how I'd do the same in Common Lisp. Here's a quick code snack that serves up a solution.
What's the problem?
The basic problem to be solved today: function names defined in the global environment with defun
are not special in the way that variable names defined with defvar
are.
In code, you can do this:
;; define a variable
> (defvar *moo* "hey")
;; define a function to print its value
> (defun print-moo ()
(print *moo*))
;; then give it a new value
> (let ((*moo* "HEY HEY"))
(print-moo))
"HEY HEY"
But it doesn't work for functions:
;; define a function
> (defun goober (name)
(format nil "goober says: Hi ~a" name))
;; define a function to call goober
> (defun call-goober (name)
(goober name))
;; dynamic rebinding doesn't work.
> (flet ((goober (name)
(format nil "goober say: Hi ~a, this is custom!" name)))
(call-goober "colin"))
"goober says: Hi colin"
A Sketched Solution
So, what you'd like to be able to do is something like:
> (dynamic-flet
(goober (name) (format nil "goober says: Hi ~a, this is custom!" name))
(call-goober "colin"))
"goober says: Hi colin, this is custom!"
Getting there involves writing a macro to automatically set the symbol-function value or fdefinition value of a function name for the a limited extent, ensuring that the original value is reset when we're done.
Something like:
;; a sketch of a solution
(defmacro dynamic-flet ((name args &rest body) &body forms)
(let ((old-function (gensym)))
`(let ((,old-function (symbol-function ',name)))
(unwind-protect
(progn
(setf (symbol-function ',name)
(function (lambda ,args ,@body)))
,@forms)
(setf (symbol-function ',name) ,old-function)))))
Which works by
- caching the original function in a temporary variable
- setting
symbol-function
for the given function to a new value - evaluating some forms
- using
unwind-protect
to ensure that the old function value is restored, even in the presence of errors.
It might be wise to make the above definition more robust. For example, you could use fbound to check whether or not the provided name is actually bound to a function. Without such measures (symbol-function 'moocow)
will signal an error when there is no function named moocow
.
Lisp Rewards the Curious
It's neat that with the hypespec in one hand, and the REPL in the other, totally new constructs can be sculpted out of Lisp as "building material".
The above sketch of a dynamic-flet
operator was not meant to be a complete implementation. I intended only to demonstrate how Common Lisp rewards curiosity in a rather unqique way. The combination of a 30 year old standard and a highly interactive environment pays dividends to anyone eager to invest the attention. I hope you'll find some time to play with, and improve upon, the above.