2

背景

我打算为我正在开发的代码生成调试消息。我写了一个宏来避免在每个函数中编写日志调用。我知道这限制了生成更多自定义调试消息的能力,但作为回报,它将日志记录与代码隔离开来。这就是我的目标。这种宏方法也有其他缺点,例如它将函数绑定的创建仅限于该宏,但我认为我可以接受。

以下是宏的定义和演示其用法的示例。

(define-syntax (define-func stx)
  (syntax-case stx ()
    [(define-func (func-name args ...) body1 body2 ...)
     (if (and (identifier? #'func-name)
              (andmap symbol? (syntax->datum #'(args ...))))
       (syntax (define (func-name args ...)
                 (log-debug (format "Function-name ~a:" (syntax-e #'func-name)) (list args ...))
                 body1
                 body2 ...))
       (raise-syntax-error 'define-func "not an identifier" stx))]
    [else (raise-syntax-error 'define-func "bad syntax" stx)]))



(define-func (last l)
  (cond [(null? l) null]
        [(null? (rest l)) (first l)]
        [else (last (rest l))]))


(define-func (main)
  (last (list 1 2 3 4 5 6 7 8 9))
  (logger))

log-debug 和 logger 在单独的模块中定义

产生的输出有点像以下:

Function-name last: 
args: 
:-> (7 8 9) 


Function-name last: 
args: 
:-> (8 9) 


Function-name last: 
args: 
:-> (9) 

现在我想让它更具可读性。通过可读性,我的意思是提供某种缩进,以便阅读日志的人可以理解呼叫流程。例如如下所示:

Function-name last: 
args: 
:-> (7 8 9) 

    Function-name last: 
    args: 
    :-> (8 9) 

        Function-name last: 
        args: 
        :-> (9) 

更容易弄清楚谁打电话给谁等等。我有一个想法可以做到这一点。它涉及一个跟踪缩进的变量,然后在记录函数名称之后,我将增加缩进,在评估主体之后,在返回值之前减少值。如下所示:

(define indent 0)

(define-syntax (define-func stx)
  (syntax-case stx ()
    [ (... ...)
      (...
      (log-debug ...)
      (increment indent)
      (let [(retval (body1 body2 ...)]
        (decrease indent)
        retval))]))

increment 和 reduction 分别增加和减少缩进。

问题

它甚至适用于返回 void 的函数。我不确定它是否是正确的行为。在球拍中 void 是一个特殊值,但我不确定创建与 void 的绑定是正确的方法。

有没有更好的方法来实现同样的目标?如果没有,这个设计有什么问题吗?只要他们将日志记录和代码分开,我对任何想法/更改都持开放态度。

谢谢您的帮助!

4

1 回答 1

4

我有几个建议给你:

  • 对于像缩进级别这样的“全局”内容,使用参数而不是变量可能会更好,因为在表达式的末尾会为您恢复原始值parameterize
  • raise-syntax-error您在宏中进行的所有这些检查都是多余的:syntax-case已经提供了保护(也称为 fenders),允许您对宏“参数”进行任何必要的验证:

    (define-syntax (define-func stx)
      (syntax-case stx ()
        [(_ (func-name args ...) body1 body2 ...)
         (andmap identifier? (syntax->list #'(func-name args ...)))
         #'(define (func-name args ...)
             (log-debug (format "Function-name ~a:" 'func-name)
                        (list args ...))
             body1
             body2 ...)]))
    

    如上所示,我还在几个地方修复了您的代码:

    1. 我使用(_ ...)而不是(define-func ...),因为在syntax-case(不像)中,后者实际上会syntax-rules绑定一个名为define-func无论如何都要养成好习惯)。
    2. 我没有将防护中的完全展平#'(args ...),而是将其转换为语法对象列表,以便您可以使用identifier?. 这比使用 测试更能揭示意图symbol?,并且允许我们也可以func-name用相同的表达式进行测试。
    3. 您不需要(syntax-e #'func-name)在扩展代码中使用!只是引用它。
于 2013-09-13T16:47:52.747 回答