I've had this question more than once before.
Generic Question
Is it possible to transparently locally shadow a function f
with a wrapper of it with the same name f
?
I.e., how to locally have (f wrapped-args...) expand to (f args...)?
Flet seems to let us do so, but has limitations, namely, the resulting wrapper is not setf-able. Is it possible to do so without resorting to flet?
Ideally there would be a macro that lets us write the "wrapped" f
calls and it expands the code to the original "non-wrapped" f
call.
At first I believed macrolet could be that, for it says in the documentation that it first expands the macro and then applies setf on the expanded form, but I'm not being able to use it (keep reading below).
Motivation
This is useful in contexts where some paremeters are implicit and should not be repeated over and over, for more DRY code.
In my previous question (let-curry) there's a particular example of that. Attempting to "automatically" assign some of the parameters of the functions (let-curry).
Caveats of flet
I got some excellent answers there, however, I hit some limitations. By resorting to flet to accomplish such local "shadowing" of the function name to a wrapper over it, such wrappers are not setf-able, thus, such wrappers cannot be used as flexibly as the original function, only to read values, not write.
Concrete question
With the link above, how can one write the macro flet-curry and have the wrapper functions be setf-able?
Bonus: Can that macro expand the wrapped calls to the original ones with 0 runtime overhead?
I tried taking the selected answer in that post and using macrolet instead of flet to no avail.
Thank you!
UPDATE
I was asked to give a concrete example for this generic question.
Comments of wishes in the code:
(locally (declare (optimize safety))
(defclass scanner ()
((source
:initarg :source
:accessor source
:type string)
(tokens
:initform nil
:accessor tokens
:type list)
(start
:initform 0
:accessor start
:type integer)
(current
:initform 0
:accessor current
:type integer)
(line
:initform 1
:accessor line
:type integer))
(:metaclass checked-class)))
(defun lox-string (scanner)
"Parse string into a token and add it to tokens"
;; Any function / defmethod / accessor can be passed to let-curry
;; 1. I'd like to add the accessor `line` to this list of curried methods:
(let-curry scanner (peek at-end-p advance source start current)
(loop while (and (char/= #\" (peek))
(not (at-end-p)))
do
;; 2. but cannot due to the incf call which calls setf:
(if (char= #\Newline (peek)) (incf (line scanner))
(advance)))
(when (at-end-p)
(lox.error::lox-error (line scanner) "Unterminated string.")
(return-from lox-string nil))
(advance) ;; consume closing \"
(add-token scanner 'STRING (subseq (source)
(1+ (start))
(1- (current))))))
Meaning I'd like let-curry
to transform any call of the curried functions in that block from
(f arg1 arg2 ...)
to(f scanner arg1 arg2 ...)
in place, as if I'd written the latter form and not the former in the source code. If that were the case with some ?macro?, then it would be setf-able by design.
It seems a macro would be the right tool for this but I don't know how.
Thanks again :)
P.S.: If you need access to the full code it's here: https://github.com/AlbertoEAF/cl-lox (scanner.lisp)