2

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

例如,假设我正在尝试识别包含化学式的字符串中的氧阴离子

我的清单可能是这样的:

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

我有理由相信,如果括号内有一个阳离子后跟一个氧(例如“(CO3)”),或者如果该阳离子后跟两个或更多氧(例如“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))))])))

感谢您能给我的任何指导!


PS这里有一些应该通过的测试:

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

1 回答 1

2

我在您的代码中看到了几个错误:

  1. 您的 *oxyanion-tests* 是一个运行时值,但您需要将其值用作函数名称标识符,因此它必须在编译时可用。
  2. syntax周围结果syntax-rules是隐含的。因此,使用,您只能获得宏模板语言(有关更多信息syntax-rules,请参阅文档)。syntax因此你不能做datum->syntax你想做的事。你必须使用syntax-case它,它允许你使用所有的 Racket 来计算你想要的语法对象。

这是我想出的:

#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->syntaxwithstx作为上下文,这意味着定义的函数将在调用宏的上下文中可用。

于 2013-05-16T06:27:42.163 回答