0

Sorry for the clumsy title but i'm having a hard time describing what i'm looking for in a few words ...

I'm working on a Common Lisp DSL project and I'm wondering if the following is possible:

The DSL might have a couple of functions

(defun foo (&rest rest))

and

(defun bar (arg))

that'll be used in the following way:

(foo (bar 100) (bar 200) (bar 300) (bar 400) (bar 500)) etc.

Now, that's a lot of redundant typing, so I wonder if it's possible to create an expander macro that'll allow for

(foo (expand bar 100 200 300 400 500))

without changing foo itself ?

EllipsenPark
  • 127
  • 9

3 Answers3

1

There is no need for a macro:

(defun foo (&rest rest)
  (apply #'+ rest)) ; for example add all together 

(defun bar (arg)
  (1+ arg)) ; 1+ is only an example here

(apply #'foo (mapcar #'bar '(100 200 300 400 500)))
wasserwerk
  • 136
  • 1
  • 4
  • Hmm true, using `apply` is always an option, even though I was wondering if it was possible without. In the context I'm thinking about it'd be more intuitive if you could use the same function (the DSL is aimed at people who don't really know CL in itself) – EllipsenPark Apr 19 '20 at 19:26
  • "Hmm true, using apply is always an option" -- not always, only for functions, not for macros. ;-) – wasserwerk Apr 20 '20 at 18:29
1

No, this can't be done without changing the signature of the functions you are defining (or possibly using some hairy thing which redefines macroexpansion): you have what I call a 'spread/nospread impedance mismatch' which can't be resolved with a standard macro in CL.

A 'nospread' function is a function which wraps all of its arguments into one formal. A 'spread' function has one formal per argument. I learned these terms when using InterLisp: they may predate that, but they seem to be mostly unused now. CL functions can be only partly-nospread ((foo bar &rest more)). Your foo is nospread.

A spread/nospread impedance mismatch is when you have a spread function but want a nospread one, or vice versa. It is almost always a sign of a design problem. The workaround for spread/nospread problems generally involves apply.

Your problem is that foo is a nospread function: it turns all its arguments into one list, but you want the macro to treat it as a spread function, handing it a single list of arguments.

In particular, in CL an expression like (x (y ...)) can never be turned into (x a1 a2 ...) for any y, function or macro (but see below), and this is what you need to happen.

In your case you want something like

(foo (expand bar a b ...)

To turn into

(foo (bar a) (bar b)

And that can't happen.

There have been Lisps which had things which were called 'splicing macros' in which a macro's expansion could be 'spliced' into a list, in the way that ,@ does for backquote. It may be that there are splicing macro packages for CL, and it may even be that they are portable: you can get a long way with *macroexpand-hook*. But standard CL macros can not do this.

  • Huh, thanks for the detaild answer, and thanks for the terminology update. That being said, looking for package turned up two that seem to do exactly what you're describing: https://quickref.common-lisp.net/cl-splicing-macro.html and https://quickref.common-lisp.net/quasiquote-2.0.html – EllipsenPark Apr 20 '20 at 13:17
  • @EllipsenPark: yes, I was vaguely aware of that. As far as I know it uses `*macroexpand-hook*` to do its work. It annoys me a little that CL doesn't have more hook-variables like that as they're very flexible: I once suggested that implementations could agree on quasi-standard ones, but people didn't like it so I gave up –  Apr 20 '20 at 13:37
0

It may be worthwhile to split your dsl into two parts, a simpler version you program against and a more user-friendly version. For example:

(in-package #:impl)
(defun foo (&rest args) ...)
(defun bar (arg) ...)

(in-package #:dsl)
(defun foo (&rest args) (apply #'impl:foo (append args)))
(defun bar (&rest args) (mapcar #'impl:bar args))
(foo (bar 100 200 300 400 500))
Dan Robertson
  • 4,315
  • 12
  • 17