如何制作可与`match`配合使用的宏?

问题描述 投票:0回答:1

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

  (case current-state
    [(state-1)
     (match symbol
       [(? predicate-1?) 
        (some-action)
        next-state]
       [(? predicate-2?) 
        (some-action)
        next-state]
       ; ...
       )]
    ; ...
    )

不过,我想使它更易于阅读,并希望使用宏。我经常使用某些谓词,并希望以较短的方式编写它们。而且我不喜欢在一系列操作结束时丢失下一个状态。我想要该信息放在首位。所以我更喜欢写这样的东西:

(case current-state
  [(state-1)
   (match symbol
     [:PRED-1: next-state
      (some-action)]
     [:PRED-2: next-state
      (some-action)]
     ; ...
     )]
  ; ...
  )

我对宏的使用不太了解,而我的早期尝试都变了。我的第一个部分尝试只是谓词宏。这是一个简单的示例:

(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:将扩展为(? is-non-zero-digit?)。我尝试的另一件事是通过定义名为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))

但是我得到了错误state-machine.rkt:220:21: ?: unbound identifier。我希望得到答案,以便为我提供一种获取所需表格的方式,并希望能对为什么我的早期尝试无法解决的解释。

racket
1个回答
1
投票

首先讨论为什么您的:NOT-0:无法正常工作。首先,宏是语法对象转换器。即,从语法对象到语法对象的功能。所以你需要写:

(define-syntax :NOT-0:
  (lambda (stx) #'(? is-non-zero-digit?)))

或使用其简写形式:

(define-syntax (:NOT-0: stx)
  #'(? is-non-zero-digit?))

但是更正后的代码也不起作用。原因是Racket宏默认情况下是“从外向内”扩展的。这意味着:

(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

大多数想要让用户扩展其功能的宏,例如foo,将提供一个“宏定义宏”,您可以使用该宏来定义bar,使foo理解应首先扩展bar 。有关技术细节,请参见Matthew Flatt等人的Macros that Work Together。>

对于您的特定问题,Racket的match提供了define-match-expander,它是定义上述宏的宏。您可以像这样使用它:

define-match-expander

注意,您需要在(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:会将其视为将匹配值绑定到的标识符。


个人,我觉得Racket的match在这里不合适。通常,当有很多match子句时,建议您改为将其转换为(? predicate)

cond

最后,如果您确实希望它以所需的形式出现,则可以创建自己的(cond [(predicate-1? symbol) ...] [(predicate-2? symbol) ...] ...) 。然后,您可以根据需要将match扩展到match或球拍的cond。另外,您将完全控制其中的子表单,从而可以交换“动作”和“状态”。这是一个小例子。

match
© www.soinside.com 2019 - 2024. All rights reserved.