Racket Macro用于自动定义给定列表的功能

时间:2013-05-15 21:14:57

标签: macros racket

我想从列表中自动生成一堆测试函数。我可以更改列表的优点(例如,通过读取CSV数据表),程序将在下一个程序执行时自动生成不同的测试。

例如,假设我尝试在包含oxyanions的字符串中标识chemical formula

我的列表可能是这样的:

(define *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")))

我有理由相信化学式中含有oxyanions中的一种,如果有一个阳离子,后面是括号内的氧(例如“(C O3)”),或者阳离子后面是2或更多的氧(例如“C O3”)。请注意,这并不完美,因为它会错过次氯酸根阴离子(例如“Cl O”),但它对我的应用来说已经足够了。

(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)))

我想我需要一个宏来做这件事,但我真的不明白他们如何通过阅读文档来工作。我在这里问,所以我有一个很好的例子来看我对我有用。

这就是我认为宏看起来应该是什么样子,但它不起作用,我真的没有心理模型来弄清楚如何解决它。

(require (for-syntax racket))
(define-syntax-rule (define-all/ate? oxyanion-tests)
  (for ([test oxyanion-tests])
    (match test
      [(list name cation) (syntax->datum (syntax (define ((string->symbol (string-append name "?")) s-formula)
                                    ((*ate? cation) s-formula))))])))

感谢你给我的任何指导!


P.S。以下是一些应该通过的测试:

(define-all/ate? *oxyanion-tests*)
(module+ test
  (require rackunit)
  (check-true (borate? "B O3"))
  (check-true (carbonate? "C O3"))
  (check-true (silicate? "Si O4")))

1 个答案:

答案 0 :(得分:2)

我在您的代码中看到了一些错误:

  1. 您的* oxyanion-tests *是一个运行时值,但您需要将其值用作函数名称标识符,因此它必须在编译时可用。
  2. syntax-rules结果周围的syntax是隐式的。因此,对于syntax-rules,您只能获得宏模板语言(有关详细信息,请参阅syntax的文档)。因此,您无法执行您尝试执行的datum->syntax。您必须使用syntax-case,这样您就可以使用所有Racket来计算所需的语法对象。
  3. 以下是我提出的建议:

    #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")))
    

    密钥是elem->fn-id函数,它将字符串转换为函数标识符。它使用datum->syntaxstx作为上下文,这意味着定义的函数将在调用宏的上下文中可用。

相关问题