【问题标题】:Generating logging information using Racket使用 Racket 生成日志信息
【发布时间】:2013-09-13 15:27:31
【问题描述】:

背景

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

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

(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 的绑定是否正确。

有没有更好的方法来达到同样的效果?如果没有,这个设计有什么问题吗?只要他们将日志记录和代码分开,我愿意接受任何想法/更改。

感谢您的帮助!

【问题讨论】:

  • 您描述的内容与trace 相似。但也许这不是你所需要的。或者,也许您想重新发明它以获得更多关于宏的学习价值。只是想确保你知道它。

标签: macros racket


【解决方案1】:

我有几个建议给你:

  • 对于像缩进级别这样的“全局”内容,最好使用parameter 而不是变量,因为在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)!只是引用它。

【讨论】:

  • 感谢您的回答。只是为了好奇,如果我继续引入新的 l​​et 绑定的解决方案,我会遇到什么问题?另外,创建对 void 值的绑定是个好主意吗?如果不是为什么 Racket 允许绑定无效?
  • let 方法存在两个问题: 1. 它不是异常安全的,因此如果您的包装函数抛出异常,计数器不会递减。 2.它不处理多个值。 void 是单个值,您可以很好地引用它的值,但如果函数返回零(或两个或更多)值,您的代码将无法正确处理。
猜你喜欢
  • 2018-10-30
  • 1970-01-01
  • 2022-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多