One thing to think about when designing things like macros (and, remember, when you are designing macros, you are designing a programming language) is how people expect to read the language. If the programming language you are designing is a mild superset of CL, or is otherwise very close to CL, then you may want to not violate the expectations CL programmers have when reading CL code, or more generally that Lisp programmers have when reading Lisp code.
[Note that most of what follows is opinion: people obviously have different opinions – these are mine, they're not more right than anyone else's.]
So, what are those expectations? Well, they may include the following two:
- people read CL left-to-right, so the things at the left-hand end of a form tend to be more visually important;
- many existing forms in CL look like
(<operator> <thing> ...) – the first two subforms in the form are by far the most interesting, and the second is often more interesting than the first. Think about (defun foo ...), (dolist (x ...) ...), (let ((x y) ...) ...).
An example of something which violates these expectations is an object system which works explicitly with message-passing, using some send operation (I think Old Flavors did this, and I think that New Flavors didn't, but my memory is vague now). Code written using these looks like (send <object> <message> ...): the first word in many forms is send. That means that this visually-important place for reading code has been entirely wasted as it is always the same word and the important places are now the second and third subform. Well, instead, we could just omit the whole send thing and write (message object ...) or (object message ...). CLOS takes essentially the first of these options, where a 'message' is a generic function and of course generic functions can specialise on more than one argument which breaks the whole message-passing paradigm. But you can write CLOS as if it was message-passing, and it works, and it means that it agrees with how a lot of other CL code looks.
So, OK let's look at two cases of your macro:
(some-macro some-name ...)
This is fine.
(some-macro :flag some-name ...)
But this has filled the visual second-position slot with something which is not whatever the macro is about: it's just some optional argument. The interesting thing is now the third position.
Well, how could we fix this? It turns out there is a good example which already exists in CL: defstruct. defstruct has two basic cases:
(defstruct structure-name ...)
and
(defstruct (structure-name ...) ...)
Both of these meet the first-two-positions-matter-most requirement, while allowing optional arguments and clearly visually indicating when they are used.
(Aside: defclasss does things differently by putting the options at the end as in:
(defclass name (...supers...) (...slot specifications...) ...options...))
Either is OK I think.)
So one way to redo your macro would be like defstruct. In this case you would have one of
(some-macro some-name (...) ...)
or
(some-macro (some-name :flag) (...) ...)
And you could implement that pretty easily:
(defmacro some-macro (thing (&rest args) &body forms) (multiple-value-bind (the-thing the-options) (etypecase thing (symbol (values thing '())) (cons (destructuring-bind (proposed-name . proposed-options) thing (unless (symbolp proposed-name) (error ...)) (unless (proper-list-p proposed-options) (error ...)) (values proposed-name proposed-options)))) ...))
In fact I would go further than this: people expect keyword arguments to have values, because in most other places they do. So instead have
(some-macro (some-name :flag t) (...) ...)
Which meets that expectation. This has the additional advantage that you can just use CL's argument parsing to get information:
> (destructuring-bind (&key (flag nil flagp)) '(:flag t) (values flag flagp)) t t
For instance. If you write the macro like this you might end up with something which looks like this:
(defmacro some-macro (thing (&rest args) &body forms) (multiple-value-bind (the-thing flag flagp) (etypecase thing (symbol (values thing nil nil)) (cons (destructuring-bind (proposed-name (&key (flag nil flagp))) thing (unless (symbolp proposed-name) (error ...)) (values proposed-name flag flagp)))) ...))
As an aside it's worth considering why defclass & defstruct do things differently, and what this means for other macros.
defstruct looks, in outline, like
(defstruct structure-name-and-options slot-description ...)
What this means is that, if you put options associated with the structure itself at the end, they're going to get confused with slot descriptions.
defclass gets around this by looking like this:
(defclass class-name (superclass-name ...) (slot-description ...) [class-option] ...)
It has nested the slot descriptions inside another list, which means that there is now space in the pattern for class options at the end of the form.
For macros which have some kind of 'body', then the natural pattern looks like
(with-foo something [some-more-special-things] ... form ...)
For example
(with-slots (sa sb) x (when (> sa sb) (setf sb (+ sa sb))) (values sa sb))
The problem here is that the whole tail of the macro form is the body which means that there is no natural place for options at the end: the only place to put them is at the beginning somewhere. You could get around this, again, by nesting the body:
(with-weird-thing x (y z) ((when y ...) (print z) ...) option ...)
But this, again, violates people's expectations: no (?) standard CL macros do this. It's significant that defclass's 'body' is not some forms: it's a list of slot specifications. So it's reasonable to adopt this pattern for defclass.
Finally it's worth considering defmethod. If I had designed this I would have done it slightly differently!
:flagsupposed to be thegeneric-name? If not, where is that in your calls?:flagcan take one of three froms in my code, let's call it:flag1,:flag2and:flag3. Depending on the flag, or no flag, the macro is expanded in a specific way. I'm not entirely familiar withgeneric-nameif it is something specific you are refering to, but hope this clears some stuff up.(defmacro some-macro (generic-name ...))generic-nameis a synonym for the first argument of my macro, then yes it is thegeneric-name. However, it is optional. Providing no flag should work as well. The exact syntax of how the macros should be called is in the second code-block of the question.