Q: Calling macro inside LOOP with list parameter.

Discussion of Common Lisp
Post Reply
smcnamara
Posts: 11
Joined: Fri Oct 10, 2008 2:48 pm

Q: Calling macro inside LOOP with list parameter.

Post by smcnamara » Tue Jul 27, 2010 7:01 am

Hi All,

I'm experiencing a problem with a macro that takes a list as one of its parameters when it's called inside a LOOP. There is obviously some cloudy thinking on my part about how macro parameters are handled, but I am having a hard time spotting where I am going wrong. A contrived example that shows my problem is given below. The macro works fine when I call it directly, but when pulling the parameters from a structured list in a LOOP, it fails to do what I expected. Trying to macroexpand it out (even in the loop) isn't showing me the issue (thought it might be right in front of me.) Any guidance is appreciated.

Thanks,
Sean

Code: Select all

;;; The code below shows a problem I'm having when trying to invoke a macro
;;; that accepts a list as one of the parameters while inside a LOOP.

;; Here is a sample macro.  It generates a class and associated stream-formatter
;; based on the NAME and SLOT-LIST parameters.

(defmacro create-class-and-printer (class slot-list)
  `(progn
     (defclass ,class () 
       ,(loop for (slot value) in slot-list
	   collect (list slot :initform (string value))))
     (defmethod print-object ((obj ,class) stream)
       (format stream "~{~a ~}" (loop for (slot nil) in 'slot-list
				    collect (slot-value obj slot))))))

;; If I invoke the macro directly, it works just fine.

(create-class-and-printer spanish-dictionary ((one uno) (two dos) (three tres)))

;; But if I try to invoke the macro while iterating through a list, it appears
;; to have trouble with the slot-list parameter, and doesn't recognize it as a
;; LIST, but printing the type shows it as a CONS.

(let ((language-list '((spanish-dictionary ((one uno) (two dos) (three tres)))
		       (french-dictionary ((one un) (two deux) (three trois)))
		       (german-dictionary ((one ein) (two zwei) (three drei)))
		       (japanese-dictionary ((one ichi) (two ni) (three san))))))
  (loop for (language dictionary) in language-list
     do (format t "Dictionary is type: ~a~%" (type-of dictionary))
     do (format t "Value of Dictionary is: ~a~%" dictionary)
     do (format t "Is this a list? ~a~%" (listp dictionary))
     do (create-class-and-printer language dictionary)))


;; I think this might have something to do with macro parameters being
;; "destructured" but am not clear on how to resolve it.  My google searches
;; haven't turned up much, which either means I'm doing something extremely
;; dumb, extremely rare, or a combination of the two.

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: Q: Calling macro inside LOOP with list parameter.

Post by ramarren » Tue Jul 27, 2010 7:45 am

Macros are compiler extensions, and so are expanded during compile time (well, macro expansion time, which is usually, but not necessarily, part of compile time). Your macro expansion has compile time side effects. Your LOOP construct is a run-time construct, an hence is irrelevant to compile time construct which is your macro. The only thing the macro sees are the two symbols you pass as arguments, which are obviously of the wrong type.

What you want to do in this case is to put LOOP construct inside the macro, so it would be able to do its work at compile time (there is also a missing comma in the first macro):

Code: Select all

(defmacro create-class-and-printer (class slot-list)
  `(progn
     (defclass ,class ()
       ,(loop for (slot value) in slot-list
              collect (list slot :initform (string value))))
     (defmethod print-object ((obj ,class) stream)
       (format stream "~{~a ~}" (loop for (slot nil) in ',slot-list
                                      collect (slot-value obj slot))))))

(defmacro list-create-class-and-printer (list)
  `(progn ,@(loop for (language dictionary) in list
                  collect `(create-class-and-printer ,language ,dictionary)
                  do (format t "Dictionary is type: ~a~%" (type-of dictionary))
                  do (format t "Value of Dictionary is: ~a~%" dictionary)
                  do (format t "Is this a list? ~a~%" (listp dictionary)))))

(list-create-class-and-printer
 ((spanish-dictionary ((one uno) (two dos) (three tres)))
  (french-dictionary ((one un) (two deux) (three trois)))
  (german-dictionary ((one ein) (two zwei) (three drei)))
  (japanese-dictionary ((one ichi) (two ni) (three san)))))
Also, often in cases like this it is better to use some other mechanism than expanding to a sequence of DEFCLASSes in a common pattern.

smcnamara
Posts: 11
Joined: Fri Oct 10, 2008 2:48 pm

Re: Q: Calling macro inside LOOP with list parameter.

Post by smcnamara » Tue Jul 27, 2010 8:45 am

Thank you Ramarren.

My actual code is creating a set of classes to handle streaming network messages, which is an idea I picked up from Rainer Joswig's video on DSL creation.

Is there a way to tweak the macro so that it can be successfully invoked inside a loop? It seems like this should be possible since creation of the macro shouldn't necessarily introduce constraints on how or when it is used.

Again, thanks for your response!

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: Q: Calling macro inside LOOP with list parameter.

Post by ramarren » Tue Jul 27, 2010 9:04 am

smcnamara wrote:Is there a way to tweak the macro so that it can be successfully invoked inside a loop? It seems like this should be possible since creation of the macro shouldn't necessarily introduce constraints on how or when it is used.
Macros are not called as such. Macros are expanded, which involves calling the macro function by the compiler. You can expand a macro inside the loop if its expansion depends only on information available during compile time. In Common Lisp compile time and run time are separated, which makes generation of high performance code much easier. The separation can be broken down by explicitly using COMPILE or EVAL, but this will usually cause performance problems and can lead to other issues.

Macros are compiler extensions. If you want to act at runtime using runtime information, then use a function. If your classes are sufficiently similar, then using a data-driven approach with closures might be better than trying to construct a class hierarchy, especially if you do not have enough information to do so at compile time.

smcnamara
Posts: 11
Joined: Fri Oct 10, 2008 2:48 pm

Re: Q: Calling macro inside LOOP with list parameter.

Post by smcnamara » Tue Jul 27, 2010 9:27 am

Ahhhh. I think the light just went on.
Ramarren wrote:You can expand a macro inside the loop if its expansion depends only on information available during compile time.
This is exactly where I had some general fuzzy thinking (and not in the nice AI sense) going on. Thank you very much!

smcnamara
Posts: 11
Joined: Fri Oct 10, 2008 2:48 pm

Re: Q: Calling macro inside LOOP with list parameter.

Post by smcnamara » Tue Jul 27, 2010 9:30 am

One more quick question:
Ramarren wrote:... using a data-driven approach with closures might be better...
Could you give me a brief explanation of what you mean by this?

Thanks again!

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: Q: Calling macro inside LOOP with list parameter.

Post by ramarren » Tue Jul 27, 2010 9:49 am

smcnamara wrote:Could you give me a brief explanation of what you mean by this?
If the behaviour of generated classes is similar enough to be generated by simple macro, then it might be better to just use hash-tables to store the data and encode the behaviour with somehow associated closures. This mostly matters if you want to construct the system at runtime, since it is more flexible, if slower and probably less clear.

smcnamara
Posts: 11
Joined: Fri Oct 10, 2008 2:48 pm

Re: Q: Calling macro inside LOOP with list parameter.

Post by smcnamara » Tue Jul 27, 2010 11:25 am

Ok, I follow. That probably doesn't make sense in this situation since I really do have all the information regarding message definition at compile time.

Thanks again.

Post Reply