Search code examples
macrosschemesyntax-rules

Scheme's syntax-rules -- intermixing different syntax choices in `...`


Let's say I have a macro (define/custom (name (arg type) ...) body ...) that among other things expands to (define (name arg ...) body ...). That's easy.

Now, I want to allow not only (arg type) to be passed as parameter, but simply arg. Alright, so I write a second clause, where (define/custom (name arg ...) body ...) is expanded to (define (name arg ...) body ...). Also easy.

But with such solution, either all arguments are with type, or none of them are. How can I allow mixing the two options in same syntax list (or whatever the ... is called)? How can I make, so that eg. (define/custom (name arg1 (arg2 type2)) #f) gets appropriately expanded to (define (name arg1 arg2) #f)? The intuition is to use a helper macro, which would expand (helper a) to a, and (helper (a b)) to a, and make (define/custom (name arg_or_arg+type ...) body ...) expand to (define (name (helper arg_or_arg+type) ...) body ...), but as you probably knew and guesses where this is coming, this doesn't work, because define expansion takes place before helper expansion.


Solution

  • You can do this with a helper macro that loops through each "arg-or-arg+type" and transforms them into (arg type) to be consistent.

    First, I recommend defining a core version of the macro that only works on the consistent (arg type) version of things:

    (define-syntax define/custom-core
      (syntax-rules ()
        ((_ (name (arg type) ...) body ...)
         ; among other things
         (define (name arg ...) body ...))))
    

    Then you can define helper macro that deals with 2 lists of input args: one for consistent (arg type) things, and another for "arg-or-arg+type" things. Example usage might look like:

    (define/custom-helper (name ((arg type) ...) (arg-or-arg+type ...)) body ...)
    

    As it loops through arg-or-arg+type ..., it will move them into the (arg type) ... list. When arg-or-arg+type ... is empty, it's done and puts all the (arg type) things into a call to define/custom-core.

    (define-syntax define/custom-helper
      (syntax-rules ()
        ((_ (name (arg+type ...) ()) body ...)
         (define/custom-core (name arg+type ...) body ...))
        ((_ (name (arg+type ...) ((arg type) . rest)) body ...)
         (define/custom-helper (name (arg+type ... (arg type)) rest) body ...))
        ((_ (name (arg+type ...) (arg . rest)) body ...)
         (define/custom-helper (name (arg+type ... (arg any)) rest) body ...))))
    

    This relies on arg being equivalent to (arg any).

    Then, all that's left is the outward-facing define/custom macro to call the helper macro. It can pass an empty arg+type list and pass the args into the arg-or-arg+type place for the helper to deal with.

    (define-syntax define/custom
      (syntax-rules ()
        ((_ (name arg-or-arg+type ...) body ...)
         (define/custom-helper (name () (arg-or-arg+type ...)) body ...))))