给定列表自动定义功能的球拍宏 [英] Racket Macro to auto-define functions given a list

查看:10
本文介绍了给定列表自动定义功能的球拍宏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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-generate different tests on the next program execution.

例如,假设我试图在包含 oxyanions="http://en.wikipedia.org/wiki/Chemical_formula" rel="nofollow">化学式.

For example, say I am trying to identify oxyanions in a string containing a chemical formula.

我的列表可能是这样的:

My list may be something like:

(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)"),或者如果阳离子后跟2个或更多个氧(例如CO3").请注意,这并不完美,因为它会遗漏次氯酸盐阴离子(例如Cl O"),但对于我的应用来说已经足够了.

I'm reasonably confident that the chemical formula contains one of these oxyanions if there is a cation followed by an oxygen within parentheses (e.g. "(C O3)" ), or if the cation is followed by 2 or more oxygens (e.g. "C O3"). Note that this isn't perfect, since it will miss hypochlorite anions (e.g. "Cl O"), but it's good enough for my application.

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

我认为我需要一个宏来执行此操作,但通过阅读文档我并不真正了解它们是如何工作的.我在这里问是为了让我有一个很好的例子来看看这对我很有用.

I think I need a macro to do this, but I don't really understand how they work from reading the documentation. I'm asking here so that I have a good example to look at that is immediately useful to me.

我认为宏应该是这样的,但它不起作用,我真的没有一个心智模型来弄清楚如何修复它.

Here is what I kind of think the macro should look like, but it doesn't work and I don't really have a mental model for figuring out how to fix it.

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

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

Thanks for any guidance you can give me!

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

P.S. Here are a few tests that should pass:

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

推荐答案

我发现您的代码中有几个错误:

I see a couple of errors in your code:

  1. 您的 *oxyanion-tests* 是一个运行时值,但您需要将其值用作函数名称标识符,因此它必须在编译时可用.
  2. syntax-rules 是隐式的.因此,使用 syntax-rules,您只能获得宏模板语言(请参阅 syntax 了解更多信息).因此,您无法执行您正在尝试执行的 datum->syntax.您必须使用 syntax-case 相反,它允许您使用所有 Racket 来计算您想要的语法对象.
  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.

这是我想出的:

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

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.

这篇关于给定列表自动定义功能的球拍宏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆