【问题标题】:Macro to record evaluation steps and intermediate values in Racket?在 Racket 中记录评估步骤和中间值的宏?
【发布时间】:2016-09-28 02:47:33
【问题描述】:

作为学习 Racket 宏系统的练习,我一直在实现一个基于 C++ catch framework 的单元测试框架。该框架的一个特点是,如果我写这样的检查:

CHECK(x == y); // (check x y)

当检查被违反时,错误消息将打印出 x 和 y 的值,即使使用的宏是完全通用的,不像其他需要您使用宏(如 CHECK_EQUALS、CHECK_GREATER 等)的测试框架。这是可能的通过一些涉及表达式模板和运算符重载的hacky。

在我看来,在 Racket 中你应该能够做得更好。在 C++ 版本中,宏看不到子表达式内部,所以如果你写这样的东西:

CHECK(f(x, g(y)) == z); // (check (= (f x (g y)) z))

当检查被违反时,您只能找到等号左右两侧的值,而不是 x、y 或 g(y) 的值。在球拍中,我希望可以递归到子表达式并打印一棵树来显示评估的每个步骤。

问题是我不知道最好的方法是什么:

  • 我已经相当熟悉语法分析,但这似乎超出了它的能力。
  • 我读到了关于自定义 #%app 的文章,这几乎是我想要的,但是如果例如 f 是一个宏,我不想打印扩展中表达式的每个评估,只打印用户调用 check 宏时可见的表达式。也不确定我是否可以在不定义语言的情况下使用它。
  • 我可以使用 syntax-parameterize 来劫持基本运算符的含义,但这对 g(y) 之类的函数调用没有帮助。
  • 我可以使用 syntax->datum 并手动遍历 AST,我自己在子表达式上调用 eval。这似乎很棘手。
  • 跟踪库几乎看起来就像我想要的那样,但你必须预先给它一个函数列表,而且它似乎不能让你控制输出的去向(我只想打印如果检查失败,而不是成功,则没有任何结果,因此我需要在执行过程中将中间值保存到一边)。

什么是最好的或至少是惯用的实现方式?

【问题讨论】:

  • 投票结束的人会解释原因吗?这个问题非常具体,我不确定如何将其解释为“广泛”

标签: macros scheme racket evaluation expression-evaluation


【解决方案1】:

这里有一些东西可以帮助您入门。

#lang racket

(require (for-syntax syntax/parse racket/list))

(begin-for-syntax
  (define (expression->subexpressions stx)
    (define expansion (local-expand stx 'expression '()))
    (syntax-parse expansion
      #:datum-literals (#%app quote)
      [x:id      (list #'x)]
      [b:boolean (list #'b)]
      [n:number  (list #'n)]
      ; insert other atoms here
      [(quote literal) (list #'literal)]
      [(#%app e ...)
       (cons stx
             (append-map expression->subexpressions (syntax->list #'(e ...))))]
      ; other forms in fully expanded syntax goes here
      [else
       (raise-syntax-error 'expression->subexpressions
                           "implement this construct"
                           stx)])))

(define-syntax (echo-and-eval stx)
  (syntax-parse stx
    [(_ expr)
     #'(begin
         (display "] ") (displayln (syntax->datum #'expr))
         (displayln expr))]))

(define-syntax (echo-and-eval-subexpressions stx)
  (syntax-parse stx
    [(_ expr)
     (define subs (expression->subexpressions #'expr))
     (with-syntax ([(sub ...) subs])
       #'(begin
           ; sub expressions
           (echo-and-eval sub)
           ...
           ; original expression
           (echo-and-eval expr)))]))


(echo-and-eval-subexpressions (+ 1 2 (* 4 5)))

输出:

] (+ 1 2 (* 4 5))
23
] +
#<procedure:+>
] 1
1
] 2
2
] (#%app * '4 '5)
20
] *
#<procedure:*>
] 4
4
] 5
5
] (+ 1 2 (* 4 5))
23

【讨论】:

    【解决方案2】:

    打印 everything 的替代方法是为应该显示的内容添加标记。这是一个粗略的简单草图:

    #lang racket
    
    (require racket/stxparam)
    
    (define-syntax-parameter ?
      (λ(stx) (raise-syntax-error '? "can only be used in a `test' context")))
    
    (define-syntax-rule (test expr)
      (let ([log '()])
        (define (log! stuff) (set! log (cons stuff log)))
        (syntax-parameterize ([? (syntax-rules ()
                                   [(_ E) (let ([r E]) (log! `(E => ,r)) r)])])
          (unless expr
            (printf "Test failure: ~s\n" 'expr)
            (for ([l (in-list (reverse log))])
              (for-each display
                        `("  " ,@(add-between (map ~s l) " ") "\n")))))))
    
    (define x 11)
    (define y 22)
    (test (equal? (? (* (? x) 2)) (? y)))
    (test (equal? (? (* (? x) 3)) (? y)))
    

    导致此输出:

    Test failure: (equal? (? (* (? x) 3)) (? y))
      x => 11
      (* (? x) 3) => 33
      y => 22
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 2020-10-21
      • 2018-09-20
      • 1970-01-01
      相关资源
      最近更新 更多