Splicing in syntax

Be gentle, folks—first time posting here.

I've been interested in DSLs for a long time, and when I get the itch I come back to Racket to fool around a bit and inevitably find myself tangled up in some issue or another. The TL;DR here is that I'm wondering if it's possible to splice syntax into an existing form of the sort:

(some-function #:some-keyword-arg some-val
   (generate-n-function-calls arg1 arg2 arg3))

; Expands to:
; (some-function #:some-keyword-arg some-val
;   (some-other-function arg1)
;   (some-other-function arg2)
;   (some-other-function arg3))

To make it more concrete, I've been working with slideshow. I wish the following were more entertaining, but it represents basically what I'm looking at:

#lang slideshow/widescreen

(slide #:title "Minimal slide"
  (item "One")
  (item "Two")
  (item "Three"))

I'm lazy, so I'd rather write:

#lang slideshow/widescreen

(define (itemize  . lst) (map item lst))

(slide #:title "Minimal slide"
  (itemize "One" "Two" "Three"))

; => slide*: argument sequence contains a bad element: '(#<pict> #<pict> #<pict>)

Ahhh, I say, so I presumably need to destructure these and maybe make a set of values? (Stare at docs a bit I don't think I want to use 'alts even though I can make it work. And why is the error coming from slide* and not slide? A bit of indirection...)

; ...
(define (itemize . lst) (apply values (map item lst)))
; ...

; => result arity mismatch;
; expected number of values not received
;  expected: 1
;  received: 3

Huh, ok. A bit weird. Let me make sure that I haven't completely forgotten how values works:

$ racket -I slideshow
Welcome to Racket v8.2 [cs]
> (apply values (map item '("1" "2" "3")))
#<pict>
#<pict>
#<pict>

Maybe I need to write a syntax transformer that would make this easier? (How naive.)

; I know, let's do this the easy way
(define-syntax-rule (itemize str0 ...)
  (begin (item str0) ...)) ; oh macro expansion is so beautiful

(slide #:title "Min slide"
  (itemize "1" "2" "3"))

; => ·3; where did ·1 and ·2 go???
; Ohhhh, right begin evaluates and only returns the last form here

So I then reasoned that I needed to splice in the syntax or else write something from the top down, like

(define-syntax (itemized-slide stx) 
   ; exercise left to the reader
   #'void)

As soon as I started down this path, I thought this would be a terrible choice: I really just want to use an existing API and not try to get too cute. But now I'm thinking hard about this issue of trying to splice in syntax and thought to approach someone, anyone, who could make this a bit more clear for me. Like all of my trials with define-syntax, it feels like I immediately hit upon something that will break hygiene and quite possibly the universe.

My intuitions here are usually wrong, and it seems like the sort of simple example where there's a trove of insights waiting to be had.

You don’t need to use macros. Just need to use apply at slide. Try

(apply slide #:title "Minimal slide"
       (itemize "One" "Two" "Three"))

with your original itemize.

Re your question about macro (which again is actually not needed here): macro can’t splice into multiple arguments on its own.

Thanks—that's lovely and simple and Just Works.

It's also obvious on the face of it, once I think a bit about it—indeed a note I wrote long ago reminds me that apply has this effect; I probably was dealing with something similar.

Re your question about macro (which again is actually not needed here): macro can’t splice into multiple arguments on its own.

Is there a reason for this?

Syntactic forms are expanded from top to down in a local manner. The enclosing form is a function call form, so each subform is further expanded as an expression. This means that the expanded begin form must be an expression, which eventually evaluates to the results of the last subexpression.

Certain contexts allow splicing. For example, definition contexts recognize begin forms specially and splice the subforms. You can create more such contexts by using Racket’s excellent syntactic extension facilities, but the bottom line is such contexts must be explicitly handled. The default function call (#%app) form apparently doesn’t support splicing.

1 Like

@usao — thanks! The explanation is helpful.

Obviously a bit to ponder on here.