3
votes

The problem is quite difficult to explain because I need to collect my thoughts, so bear with me. I've been able to reduce the problem to a minimal example for illustrative purposes. The example will not make any sense as to what this would be useful for, but I digress. Say I want to extend the racket language to write things that look like this:

(define-something
  (['a] 'whatever)
  (['b 'c] 'whatever2))

Between the square brackets is a sequence of one or more symbols, followed by a sequence of racket expressions (the whatever's, which are not important for the problem statement)

The example would match a macro that looks something like this:

(define-syntax (define-something stx)
  (syntax-case stx ()
    [(_  ([symb ...] body ...) ...)
     #'()]))

Actually here we match 0 or more symbols, but we can assume there is always going to be at least one.

In the macro's body I want to generate function definitions using the concatenated symbols as the identifier. So for our silly example the macro would expand to something like:

(define (a) 'whatever)
(define (bc) 'whatever2)

I have found a similar question where the poster generates functions using a pre-defined list of strings, but I am not that fluent with macro's so I have not been able to translate the concepts to solve my problem. I thought perhaps I could try generating a similar list (by concatenating the symbols) and applying their tactic, but I've been getting way too confused with all the ellipses in my macro definition. I'm also a bit confused about their use of an ellipsis in with-syntax.

1
Is this just an exercise to help learn macros, or is there any purpose to it? It doesn’t seem terribly useful in the stated form. - Alexis King
What I'm actually trying to do is in the context of reactive programming. The symbols are actually reactive signals (which I can probably reduce to a unique symbol for each signal). When one signal changes (e.g. signal 'a') I execute body 'whatever'. When both signals 'a' and 'c' change (and a body is defined for 'a' and 'c') I want to execute 'whatever2. Once the methods have been generated as part of a racket class it's actually quite easy to invoke them using dynamic-send (which accepts a symbol method name). Sorry, it requires a lot more explanation, but I'm still experimenting myself. - Sam
One other (major) reason why I want to generate methods is so that I can very easily add parameters to the methods. These parameters can then be used in the generated bodies of the method as if they just "exist", and I would not have to worry about introducing these "magic variables" in another way. If you want more info, or a reasonable explanation that contains more characters than a tweet, I'd be happy to provide it! - Sam

1 Answers

4
votes

It’s possible to solve this with with-syntax and syntax-case, but the easiest way to do this is by using syntax-parse’s syntax classes. By defining a syntax class that parses a list of symbols and produces a single concatenated identifier, you can lift the symbol parsing out of the macro body:

(require (for-syntax syntax/parse
                     racket/string))

(begin-for-syntax
  (define-syntax-class sym-list
    #:attributes [concatenated-id]
    (pattern (~and stx (sym:id ...))
             #:attr concatenated-id
             (let* ([syms (syntax->datum #'(sym ...))]
                    [strs (map symbol->string syms)]
                    [str (string-append* strs)]
                    [sym (string->symbol str)])
               (datum->syntax #'stx sym #'stx #'stx)))))

Now you can define your macro pretty easily:

(define-syntax (define-something stx)
  (syntax-parse stx
    [(_ (syms:sym-list body ...) ...)
     #'(begin
         (define (syms.concatenated-id) body ...)
         ...)]))

Note that this uses unquoted symbols in the name clause, so it would work like this:

(define-something
  ([a] 'whatever)
  ([b c] 'whatever2))

The names can’t be expressions that evaluate to symbols because the information needs to be known at compile-time to be available to macro expansion. Since you mentioned in a comment that this is for an FRP-like system, your signal graph will need to be static, like Elm’s is for example. If you want the ability to construct a dynamic signal graph, you’ll need a more complex strategy than macros since that information will need to be resolved at runtime.