Racket 如何使宏与“匹配”一起工作?

Racket 如何使宏与“匹配”一起工作?,racket,Racket,我正试图为我使用racket/gui制作的计算器编写一些类似有限状态机的东西,我决定混合使用case和match来实现它。对于特定的状态和符号,我将执行一些任意代码并返回机器的下一个状态。一个简单的例子: (case current-state [(state-1) (match symbol [(? predicate-1?) (some-action) next-state] [(? predicate-2?

我正试图为我使用
racket/gui
制作的计算器编写一些类似有限状态机的东西,我决定混合使用
case
match
来实现它。对于特定的状态和符号,我将执行一些任意代码并返回机器的下一个状态。一个简单的例子:

  (case current-state
    [(state-1)
     (match symbol
       [(? predicate-1?) 
        (some-action)
        next-state]
       [(? predicate-2?) 
        (some-action)
        next-state]
       ; ...
       )]
    ; ...
    )
(define (in-list value lst)
  (if (list? (member value lst))
    #true
    #false))
(define (is-non-zero-digit? symbol)
  (in-list symbol '(1 2 3 4 5 6 7 8 9)))
(define-syntax :NOT-0:
  #'(? is-non-zero-digit?))

(match 0
  [:NOT-0: 'wrong]
  [_ 'right])
; 'wrong
不过,我想让它更容易阅读,并想玩弄宏。我将经常使用一些谓词,并希望用更短的方式编写它们。我不喜欢在一系列行动结束时失去下一个状态。我想把那个信息放在前面和中间。所以我更喜欢写一些类似的东西:

(case current-state
  [(state-1)
   (match symbol
     [:PRED-1: next-state
      (some-action)]
     [:PRED-2: next-state
      (some-action)]
     ; ...
     )]
  ; ...
  )
我对宏不是很有经验,我早期的尝试都出了问题。我的第一次部分尝试只是谓词宏。下面是一个简单的例子:

  (case current-state
    [(state-1)
     (match symbol
       [(? predicate-1?) 
        (some-action)
        next-state]
       [(? predicate-2?) 
        (some-action)
        next-state]
       ; ...
       )]
    ; ...
    )
(define (in-list value lst)
  (if (list? (member value lst))
    #true
    #false))
(define (is-non-zero-digit? symbol)
  (in-list symbol '(1 2 3 4 5 6 7 8 9)))
(define-syntax :NOT-0:
  #'(? is-non-zero-digit?))

(match 0
  [:NOT-0: 'wrong]
  [_ 'right])
; 'wrong
我不知道为什么会这样。我想
:NOT-0:
会扩展到
(?是非零位?
)。我尝试的另一件事是通过定义名为
transition
的宏来获得所需的顺序:

; defined earlier in file
(define-syntax-rule
  (transition pattern next-state action ...)
  [pattern action ... next-state])
; ...
; the below is from a rackunit test
(define a-variable 0)
(define (side-effect)
  (set! a-variable 1))
(define result
  (match 0
    (transition (? is-non-zero-digit?) 'wrong (side-effect))
    [_ 'right]))
(check-equal? result 'right)
(check-equal? a-variable 1))

但是我得到了错误
状态机。rkt:220:21:?:未绑定标识符
。我希望答案能为我提供一种获取所需表单的方法,并希望您能解释我之前的尝试失败的原因。

让我们先谈谈您的
:NOT-0:
失败的原因。首先,宏是语法对象转换器。也就是说,从语法对象到语法对象的函数。所以你需要写:

(define-syntax :NOT-0:
  (lambda (stx) #'(? is-non-zero-digit?)))
或使用其速记形式:

(define-syntax (:NOT-0: stx)
  #'(? is-non-zero-digit?))
但是修正后的代码也不太管用。原因是球拍宏在默认情况下是“从外到内”展开的。这意味着:

(define-syntax-rule (foo (#:foo x))
  x)

(define-syntax-rule (bar x)
  (#:foo x))

(foo (bar 1)) ; doesn't work, because `foo` is expanded first, and it couldn't find #:foo
大多数想让用户扩展its功能的宏,如
foo
,都会提供一个“宏定义宏”,您可以使用它来定义
bar
,从而
foo
理解应该首先扩展
bar
。有关技术细节,请参见Matthew Flatt等人的文章

对于您的特定问题,Racket的
match
提供了一个宏定义宏,我在上面介绍过。您可以这样使用它:

(define-match-expander :NOT-0:
  ;; can also use syntax-case on stx to further ensure that stx must have a particular shape.
  (lambda (stx) #'(? is-non-zero-digit?)))

(define (is-non-zero-digit? symbol)
  ;; no need to define in-list. member alone would suffice
  (member symbol '(1 2 3 4 5 6 7 8 9)))

(match 0
  [(:NOT-0:) 'wrong]
  [_ 'right])
请注意,在
:NOT-0:
周围需要括号。如果您有一个空的
:NOT-0:
match
将把它作为一个标识符来绑定匹配值


就我个人而言,我觉得Racket的
匹配在这里并不合适。通常,当存在大量
(?谓词)
子句时,建议您将其转换为
cond

(cond
  [(predicate-1? symbol) ...]
  [(predicate-2? symbol) ...]
  ...)
最后,如果您真的希望它以您想要的形式出现,您可以创建自己的
匹配。您可以根据需要将您的
match
扩展到
cond
或球拍的
match
。作为奖励,您将完全控制其中的子窗体,允许您交换“操作”和“状态”。这里有一个小例子

(define-syntax-rule (match e [pred e*] ... [#:else e-else])
  (let ([v e]) ; so that we evaluate e only once
    (cond [(pred v) e*] ... [else e-else])))

(match 0
  [is-non-zero-digit? 'wrong]
  [#:else 'right])

(require (only-in racket/match [match r:match]))
;; Racket's match is still available via r:match

按照@sorawee的建议,如果有人对我的最终结果感兴趣,我自己制作了宏:

(define-syntax-rule 
  (transition to-evaluate
                 [pred next-symbol body ... ] ... 
                 [#:else else-next-symbol else-body ...])
  (let ([v to-evaluate])
    (cond 
      [(pred v) body ... next-symbol] ... 
      [else else-body ... else-next-symbol])))
它将转换表达式,如:

(transition symbol
  [is-digit?  '1st-operand
    (send accumulator-register push symbol)]
  [is-operator?  'got-op
    (set! current-operator symbol)]
  [#:else (clear-calculator)])


我使用match是为了避免创建一个全新的表单。我希望@sorawee创建一个宏,我可以将其放入现有的表单中。我想,如果我尝试将
:NOT-0:
cond
一起使用,那么我必须将它扩展为包含
符号
,我知道这不会很好地工作。我认为这有违“卫生”。。。我并不清楚这意味着什么。在您的示例foo宏中,
#:foo
是什么?它像lambdas中的可选参数吗?或者您正在指定模式
(#:foo x)
?如果是后者,我感到困惑,我认为模式必须以语法规则中给定的
id
开始。我想我理解你所说的“由外而内”是什么意思,但并不完全理解。我从我的程序中提取了足够的数据,可以使用
:NOT-0:
,并通过宏步进器运行它。虽然我不完全理解会发生什么,但似乎
:NOT-0:
在首先完全展开
match
之前不会展开。听起来像你说的“由外而内”。是的。由外而内就是这个意思。外部宏将首先展开。
#:foo
是一个关键字。它通常在lambda中用于关键字参数,但通常也可以在语法中使用。例如,在
For
语法中,可以执行类似于
(For([x10]#:when(odd?x))(displayln x))
的操作。