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.