Racket Macro to auto-define functions given a list

前端 未结 1 818
生来不讨喜
生来不讨喜 2021-01-15 10:46

I want to auto-generate a bunch of test functions from a list. The advantage being I can change the list (e.g. by reading in a CSV data table) and the program will auto-gen

相关标签:
1条回答
  • 2021-01-15 11:40

    I see a couple of errors in your code:

    1. Your *oxyanion-tests* is a runtime value, but you need its values to use as function name identifiers, so it must be available at compile time.
    2. The syntax around the result of syntax-rules is implicit. So with syntax-rules, you only get the macro template language (see the docs for syntax for more info). Thus you can't do the datum->syntax that you are trying to do. You have to use syntax-case instead, which allows you to use all of Racket to compute the syntax objects you want.

    Here's what I came up with:

    #lang racket
    (require (for-syntax racket/syntax)) ; for format-id
    
    (define-for-syntax *oxyanion-tests*
      ;           name         cation
      (list (list "aluminate"  "Al")
            (list "borate"     "B")
            (list "gallate"    "Ga")
            (list "germanate"  "Ge")
            (list "phosphate"  "P")
            (list "sulfate"    "S")
            (list "silicate"   "Si")
            (list "titanate"   "Ti")
            (list "vanadate"   "V")
            (list "stannate"   "Sn")
            (list "carbonate"  "C")
            (list "molybdate"  "Mo")
            (list "tungstate"  "W")))
    
    (define ((*ate? elem) s-formula)
      (or (regexp-match? 
           (regexp (string-append "\\(" elem "[0-9.]* O[0-9.]*\\)")) 
           s-formula)
          (regexp-match?
           (regexp (string-append "(^| )" elem "[0-9.]* O[2-9][0-9.]*")) 
           s-formula)))
    
    (define-syntax (define-all/ate? stx)
      (syntax-case stx ()
        [(_)
         (let ([elem->fn-id 
                (λ (elem-str)
                  (format-id 
                   stx "~a?" 
                   (datum->syntax stx (string->symbol elem-str))))])
           (with-syntax 
             ([((ate? cation) ...)
               (map 
                (λ (elem+cation)
                  (define elem (car elem+cation))
                  (define cation (cadr elem+cation))
                  (list (elem->fn-id elem) cation))
                *oxyanion-tests*)])
             #`(begin
                 (define (ate? sform) ((*ate? cation) sform))
                 ...)))]))
    
    (define-all/ate?)
    (module+ test
      (require rackunit)
      (check-true (borate? "B O3"))
      (check-true (carbonate? "C O3"))
      (check-true (silicate? "Si O4")))
    

    The key is the elem->fn-id function, which turns a string into a function identifier. It uses datum->syntax with stx as the context, meaning the defined function will be available in the context where the macro is invoked.

    0 讨论(0)
提交回复
热议问题