Reputation: 11947
At first, I expected it to be simple - given it is Common Lisp with the most powerful of all macro facilities of all languages. But now, 8 hours later, I have commas and quasi quotes coming out of my back side and I am no where closer to the solution.
I need a macro which produces 3 functions. Its signature looks like this:
(defmacro remote (name arg-names &body body) ...)
And it is supposed to generate (I use "foo" as name argument in the text below):
(defun foo (,arg-names) ,@body )
(just the direct implementation of foo) which can be invoked as (foo arg1 arg2 ... )
.
(defun remote-foo (args) ...
which can be invoked as (remote-foo arg1 arg2 ...)
and returns the quoted call. (foo arg1 arg2 ...)
(defun remote-defun-foo () ...
which, when invoked like so (remote-defun-foo)
returns the quoted definition of the function (defun foo (,args) ,@body)
.
I found a solution for 1. But I remain clue less as to how to write up 2. and 3. Here is what I have so far.
(defun make-remote-name (name)
(read-from-string (format nil "remote-~a" name)))
(defun make-definition-name (name)
(read-from-string (format nil "remote-defun-~a" name)))
(defmacro remoted (name arg-names &body body)
`(progn
(defun ,name ,arg-names ,@body) ;; 1.
))
I am not sure - but for 2. and 3. I seem to need something like nested quasi quotes or other tricks I never used before.
Upvotes: 1
Views: 261
Reputation:
As described this is not hard:
(defmacro define-remote (name (&rest args) &body decls/forms)
(let ((remote-name (intern (format nil "REMOTE-~A" (symbol-name name))))
(remote-defun-name (intern (format nil "REMOTE-DEFUN-~A"
(symbol-name name))))
(def `(defun ,name (,@args) ,@decls/forms)))
`(progn
,def
(defun ,remote-name (,@args) `(,',name ,,@args))
(defun ,remote-defun-name ()
',def)
',name)))
Then
> (macroexpand '(define-remote foo (x) x))
(progn
(defun foo (x) x)
(defun remote-foo (x) `(foo ,x))
(defun remote-defun-foo () '(defun foo (x) x))
'foo)
t
The fiddly bit (which an earlier version of this answer got wrong as I'd misread the question) is the second form, where you want to create a quoted call using the arguments from the function you've created, so you have to fiddle about with nested backquotes a bit.
A way which I find helps to understand this is to rewrite the second form in terms of list
, and then remember that
(list x y)
is the same as
`(,x ,y)
(I have no idea how to get backquote into inline code in markdown, it turns out.)
However all of these have a nasty lurking problem: the second form in the expansion is generally going to be wrong. Consider this:
> (macroexpand '(define-remote foo (&key (x 1)) x))
(progn
(defun foo (&key (x 1)) x)
(defun remote-foo (&key (x 1)) `(foo ,&key ,(x 1)))
(defun remote-defun-foo () '(defun foo (&key (x 1)) x))
'foo)
t
The remote-foo
form is wrong because it splices in the lambda list keywords. Almost certainly the expansion should be
(progn
(defun foo (&key (x 1)) x)
(defun remote-foo (&key (x 1)) `(foo :x ,x))
(defun remote-defun-foo () '(defun foo (&key (x 1)) x))
'foo)
Getting that right is not simple I think: you need something that can take a lambda-list and turn it into an arglist which corresponds to it, and I don't think that exists in CL as a precanned thing. I'm sure it exists as something someone has written however, I just don't know where.
As an example of why this is not simple, consider this
(define-remote foo (&key (x 1 xp) ...))
Now remote-foo
probably needs to be
(defun remote-foo (&key (x 1 xp))
(if xp
`(foo :x ,x)
'(foo)))
One way of dealing with this is simply to forbid lambda-list keywords:
(defmacro define-remote (name (&rest args) &body decls/forms)
(dolist (a args)
(when (member a lambda-list-keywords)
(error "can't hack lambda list keywords in ~A, sorry" args)))
(let ((remote-name (intern (format nil "REMOTE-~A" (symbol-name name))))
(remote-defun-name (intern (format nil "REMOTE-DEFUN-~A"
(symbol-name name))))
(def `(defun ,name (,@args) ,@decls/forms)))
`(progn
,def
(defun ,remote-name (,@args) `(,',name ,,@args))
(defun ,remote-defun-name ()
',def)
',name)))
This version of the macro is limited but correct.
Upvotes: 3