2013-05-15 35 views
2

我想从列表中自动生成一堆测试函数。好处是我可以更改列表(例如通过读取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个或更多个氧原子(例如”C03“)。请注意,这并不完美,因为它会漏掉次氯酸盐阴离子(例如“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))))]))) 

感谢您给我的任何指导!


附:下面是应该通过一些测试:

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

回答

2

我看到一对夫妇的错误在你的代码:

  1. 你*氧离子的测试*是一个运行时的值,但你需要它的值用作为函数名称标识符,所以它必须在编译时可用。
  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->syntaxstx作为上下文,这意味着定义的函数将在宏调用的上下文中可用。