3

I am putting together a macro to generate simple functions of the style:

(defun hello ()
  (format t "hello~&"))

Each new function will have hello replaced.

(defmacro generate-echoers (list)
  `(let ((list-of-functions
         (loop for var in ,list
            collect
              `(defun ,(intern var) ()
                 (format t ,(concatenate var "~&"))))))
  `(progn
     ,list-of-functions)))

I developed the above function, which demonstrates conclusively that I have not yet masted quote-times and phases of expansion.

The desired usage is as follows: (generate-echoers '("hi" "ping" "pong")) => ;A list of functions that each say their name, as HELLO does above.

3 Answers 3

7

A function to generate:

(defun hello ()
  (format t "hello~&"))

I would first write a function which creates above code:

(defun make-echoers (name)
  `(defun ,(intern (string-upcase name)) ()
     (format t ,(concatenate 'string name "~&"))))

Note that symbols are by default uppercase in Common Lisp - so we are using uppercase, too.

Then you can test it:

CL-USER 1 > (make-echoers "hello")
(DEFUN HELLO NIL (FORMAT T "hello~&"))

Works. Now let's use it:

(defmacro generate-echoers (list)
  `(progn ,@(mapcar #'make-echoers list)))

Test it:

CL-USER 2 > (macroexpand-1 '(generate-echoers ("hi" "ping" "pong")))
(PROGN
  (DEFUN HI NIL (FORMAT T "hi~&"))
  (DEFUN PING NIL (FORMAT T "ping~&"))
  (DEFUN PONG NIL (FORMAT T "pong~&")))
Sign up to request clarification or add additional context in comments.

3 Comments

Interesting, I didn't think of making a defun to generate the defun. Problem's already solved, but +1!
apologies for thread necro; is it possible to alter this so that one can pass a symbol whose value is the list ("hi" "ping" "pong"), rather than pass the list directly? I tried using ,list, but this failed.
@James Porter: you would need to make sure that the symbol value is retrieved. For example by changing the macro to check for a symbol and trying to retrieve a symbol value. Opens another can of worms...
4

Your code can be simplified and made more correct like this:

(defmacro generate-echoers (list)
  `(progn ,@(loop :for var :in list
               :collect `(defun ,(intern (format nil "~:@(~A~)" var)) ()
                           (format t ,(concatenate 'string var "~&"))))))

First of all, you've got to splice the loop's result into the generated body.

Also you've forgotten, that concatenate takes type parameter and to upcase all your vars (otherwise you'll get function names like |foo|).

1 Comment

The `(progn ,@(loop solved it for me. The rest I sorted out on my own. Thanks a lot!
1

If you pass symbols to the generate-echoers macro (instead of strings), the intern call is no longer necessary:

(defmacro generate-echoers (&rest echoers)
  `(progn
     ,@(mapcar (lambda (var)
                 `(defun ,var () 
                    (format t ,(format nil "~(~a~)~&" var))))
               echoers)))

2 Comments

The name of the function needs to be SYMBOL or a list according to the Hyperspec. I don't grasp the list part, I think it has to be setf'able. I was passing in a list of strings. I bet you were passing in a list of symbols?
Also, I like the mapcar. Much cleaner than loop here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.