Can you do simple unquote in lisp

Viewed 74

I would try to re-use some Xlisp-stat program and like to transfrom the following

(defproto mls-proto '(id upper lowers logs histories)))

into

(defclass mls-proto (id upper lowers logs histories))
; or
(defgeneric mls-proto (id upper lowers logs histories))

For my next stage trying to do defmeth to defmethod.

The first issue is how to unqote the quoted list. Seems not easy as those ` and @ and ,@ seems not enough.

(defmacro defproto-1 (name &body body)
  `(defclass ,name () ,@body ))

just becomes

(DEFCLASS MLS-PROTO () '(ID UPPER LOWERS LOGS HISTORIES))

Any hint how to move on?

Really like to become

 (DEFCLASS MLS-PROTO () (ID UPPER LOWERS LOGS HISTORIES))

Based on the first answer, I adopt to fix my problem as below:

(defmacro defproto-0 (name (quote slot-names))
  (unless (eq quote 'quote)
    (error "doomed"))
  `(defclass ,name () (,@slot-names)))

(macroexpand-1 '(defproto-0 mls-proto '(id upper lowers logs histories)))

(defproto-0 mls-proto '(id upper lowers logs histories))

But based on both answers, the general solution might be very hard. My concern is really just to migrate some programs under https://www.jasss.org/2/1/3.html . As point out by both answers, I think I might be dealing with an issue which is harder than I thought. Even though suggestion is good enough and thanks for both, move on the even harder question of defmeth might not be that easy. One might think one should be just re-write them; that might be easier.

2 Answers

First of all, let's recall if necessary that the apostrophe ' character is a reader macro that reads the next form F and produces a regular Lisp form (quote F). That's why there is a quote symbol below.

So, in the following form:

(defproto-1 mls-proto '(id upper lowers logs histories)))

defproto-1 is given two unevaluated values:

  1. the symbol mis-proto

  2. a body, ie. a list of all remaining arguments; here there is only one, so the value bound to body is a list like this:

     ((quote (id upper lowers logs histories)))
    

Here above I wrote the list as it is at read-time, not how you need to write it if you want Lisp to evaluate it.

This is important to make this distinction because forms that are in parentheses are usually evaluated and we need to put quotes before them to evaluate them as literal lists.

The reason body is bound to this list is because macros don't evaluate their arguments. In the macro call you have '(id upper lowers logs histories), which is thus literally the first element of the list in body.

When you splice the body in the resulting code, all its elements are placed one after the other in the parent form, and you reintroduce the (quote ...) form inside defclass.

If you change the macro as follows:

(defmacro defproto-2 (name slots)
   ...)

Then you remove one layer of complexity: you expect the caller to give only one argument to defproto-2 instead of a &body. Then, in the following call:

(defproto-2 mls-proto '(id upper lowers logs histories)))

The slots variable would be bound to:

'(id upper lowers logs histories)

aka.

(quote (id upper lowers logs histories))

You still cannot splice it directly, because there's a quote in front of your list, but you could access the list by calling (second slots), skipping the first element and using the quoted list (which you can splice).

However, if you do so, you are assuming the callers always gives you a quoted list. This could be a problem if there are instances of defproto in your code that are as follows:

(defproto name some-list-var)

or

(defproto name (f x y z))

... where some-list-var is a symbol (bound to a list of slot names) and (f x y z) some function call.

In that case, your macro would never be able to work, since a variable has a value at runtime, not earlier (this might work if the expression can be evaluated at compile time, in which case you could (eval slots) in your macro, but this is a special case).

I'm also assuming you are working with some existing code where you cannot change all the calls, otherwise you could simplify them a bit and write the macro as:

;; &body version, body is bound to a list of symbols
(defproto mis-proto id upper lowers logs histories)

or

;; slots version, where the variable is a literal list
(defproto mis-proto (id upper lowers logs histories))

If indeed there are instances of calls where the list of slots is not a quoted list but a different form that needs to be evaluated to obtain a list, then your macro needs to expand into code that can create a class given a dynamic list of slots.

In Common Lisp, with the Meta-Object Protocol, that would be mop:ensure-class. In your case you might need to expand into a form that looks like (eval (list 'defclass ...)), where the defclass body contains as many slots as necessary.

You have two problems, one small, one very large, as well as probably a really enormous one lurking in the background. I am presuming that your target language is CL.

The small one is that your form looks like this:

(defproto mls-proto '(id upper lowers logs histories)))

Which in fact is like this:

(defproto mls-proto (quote (id upper lowers logs histories))))

So, well, you can use destructuring in the macro argument list to 'deal' with this problem:

(defmacro defproto (name (quote slot-names))
  (unless (eq quote 'quote)
    (error "doomed"))
  `(defstruct ,name ,@slot-names))

And now

(defproto mls-proto (quote (id upper lowers logs histories))))
->
(defstruct mls-proto id upper lowers logs histories)

So that 'solves' the problem. Except it doesn't. Because, presumably, the original source could do this:

(defvar *slots* '(a b c))
...
(defproto mls-proto *slots*)

And this is going to completely fail. This is the very large problem.

There are two sane ways of dealing with this:

The first is to have a class in which the xlisp-level slots are managed explicitly. The second is to use the metaobject protocol to define classes programmatically. That might look like

(defmacro defproto (name slot-names)
  `(ensure-class ',name :direct-slots (mapcar #'canonicalise-xlisp-slot-spec
                                              ,slot-names)))

Where I haven't written canonicalise-xlisp-slot-spec.

And, finally, the really enormous problem is that it's unlikely that the semantics of things defined in XLISP-STAT are anything like the semantics of things defined using CLOS: doing a translation is not likely to be simple.

Related