> I would love to see some library provide better abstractions for > fairly common cases of keywords like this Honestly, I don't think I need a library, I need better knowledge. Your macro works, but I have no idea how I could have come up with it. The Racket guide gives a good introduction to the language, but when it comes to the macro system which is basically a language on its own, all I get is one chapter in the the guide and a huge reference manual. How did other people learn Racket meta programming? Read the implementation? I feel like I have opened a box full of electronics parts and all parts are labeled and documented perfectly, but I have no idea what any of those labels mean.
It looks like your macro is turning strings into symbols. I changed (define-syntax (register-nvim-function! stx) (displayln stx) #`(begin)) into (define-syntax (register-nvim-function! stx) (displayln (syntax->datum stx)) #`(begin)) so that it would print the list-representation of what it got, and all the strings are symbols: > (define-nvim-function (foo bar baz) #:name "_foo" #:eval (void) #:sync "hello" (display bar) (display baz)) (register-nvim-function! foo _foo #:eval (void) #:sync hello) This is not much of an issue since symbols are packed into strings, but it is a weak spot. > You would probably then want to add contracts using `expr/c`. Yes, once I can wrap my head around how the macro works I can harden it. > I also wonder if you really want to have a mandatory "name" argument > or if you should infer it based on the identifier being defined > (perhaps with an option to override). I was considering that, but the Racket conventions for function names (lower-case, using hyphens) are not valid Neovim function names. The user would either have to use un-Racketey names in Racket code or specify the name any time, so I might as well make the name mandatory. > here is how I might write this: Let me see if I understand how your macro works. (define-splicing-syntax-class options #:attributes (name [opt 1]) (pattern (~seq (~alt (~once (~seq #:name name:expr)) (~optional (~seq #:range range:expr)) (~optional (~seq #:eval eval:expr)) (~optional (~seq #:sync sync?:expr))) ...) #:with (opt ...) #`(#,@(for*/list ([clause (in-list (list (attr? #:range range) (attr? #:eval eval) (attr? #:sync sync?)))] #:when clause [term (in-list clause)]) term)))) This is the syntax class of all the keyword-value pairs. My first mistake was using `~alt`, I should have used `(~seq (~alt ...))` instead because I accept a sequence of pairs, not just one pair. The syntax class also matches all the pair at once instead of each class instance matching only one pair. The `#:with` directive mathes the `opt ...` pattern against a syntax object which has been constructed out of all the non-name pairs. But how does the `name` attribute get its value? Because it's not an `opt`? (syntax-parse stx [(_ raw-lhs:expr opts:options body:expr ...+) The `raw-lhs` is the thing we want defined. How does it know to match both `foo` and `(foo bar baz)`? #:do [(define-values (ident-stx rhs-stx) (normalize-definition #'(define raw-lhs body ...) #'λ #t #t))] I don't understand why we need this. It takes apart a definition we built on the fly, takes it apart, but in the template we put thing back together in the same order: #'(begin (define ident rhs) (register-function! ident opts.name opts.opt ...))])) -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.