BitTickler
BitTickler

Reputation: 11947

Defining a macro which defines a set of functions and function calls in Common Lisp

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):

  1. (defun foo (,arg-names) ,@body ) (just the direct implementation of foo) which can be invoked as (foo arg1 arg2 ... ).

  2. (defun remote-foo (args) ... which can be invoked as (remote-foo arg1 arg2 ...) and returns the quoted call. (foo arg1 arg2 ...)

  3. (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

Answers (1)

user5920214
user5920214

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

Related Questions