Macros 利用Racket生成测井信息

Macros 利用Racket生成测井信息,macros,racket,Macros,Racket,背景: 我打算为我正在开发的代码生成调试消息。我编写了一个宏,以避免在每个函数中写入日志调用。我知道这限制了生成更多定制调试消息的能力,但作为回报,它将日志记录与代码隔离开来。这就是我的目标。这种宏方法也有其他缺点,例如,它限制了只创建这个宏的函数绑定,但我认为我可以接受 下面是宏的定义和演示其用法的示例 (define-syntax (define-func stx) (syntax-case stx () [(define-func (func-name args ...) bo

背景

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

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

(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))
日志调试和日志记录器在单独的模块中定义

产生的输出有点类似于以下内容:

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))]))
现在我想让它更具可读性。所谓可读性,我的意思是提供某种缩进,以便阅读日志的人能够理解调用流。例如,如下所示:

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))]))
更容易弄清楚谁给谁打电话等等。我有一个想法可以做到这一点。它包含一个跟踪缩进的变量,然后在记录函数名之后,我将增加缩进,在对body求值之后,在返回值之前,减少值。大致如下:

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))]))
增加和减少分别增加和减少压痕

问题

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

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


谢谢你的帮助

我有几点建议给你:

  • 对于缩进级别之类的“全局”内容,最好使用一个而不是一个变量,因为原始值会在
    参数化表达式的末尾恢复
  • 宏中的所有
    raise syntax error
    检查都是多余的:
    syntax case
    已经提供了防护装置(也称为挡泥板),允许您对宏“参数”进行任何必要的验证:

    (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 ...)]))
    
    我还在几个地方修复了您的代码,正如您在上面看到的:

  • 我使用了
    (…
    而不是
    (define func…
    ),因为在
    语法情况下
    (不同于
    语法规则
    ),后者将实际绑定名为
    define func
    的模式变量,这将影响您可能要执行的任何递归宏调用(我承认你这里没有,但无论如何这是个好习惯)
  • 我没有将guard中的
    #'(args…
    )完全展平,而是将其转换为语法对象列表,以便您可以使用
    标识符?
    进行测试。这比使用
    符号?
    进行测试更能揭示意图,并允许我们在同一表达式中测试
    func name
  • 您不需要在扩展的代码中使用
    (syntax-e#'func-name)
    ,只需引用即可

    • 我有几点建议:

      • 对于缩进级别之类的“全局”内容,最好使用一个而不是一个变量,因为原始值会在
        参数化表达式的末尾恢复
      • 宏中的所有
        raise syntax error
        检查都是多余的:
        syntax case
        已经提供了防护装置(也称为挡泥板),允许您对宏“参数”进行任何必要的验证:

        (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 ...)]))
        
        我还在几个地方修复了您的代码,正如您在上面看到的:

      • 我使用了
        (…
        而不是
        (define func…
        ),因为在
        语法情况下
        (不同于
        语法规则
        ),后者将实际绑定名为
        define func
        的模式变量,这将影响您可能要执行的任何递归宏调用(我承认你这里没有,但无论如何这是个好习惯)
      • 我没有将guard中的
        #'(args…
        )完全展平,而是将其转换为语法对象列表,以便您可以使用
        标识符?
        进行测试。这比使用
        符号?
        进行测试更能揭示意图,并允许我们在同一表达式中测试
        func name
      • 您不需要在扩展的代码中使用
        (syntax-e#'func-name)
        ,只需引用即可

      谢谢你的回答。出于好奇,如果我继续引入新的let绑定解决方案,我会遇到什么问题?另外,创建一个空值绑定是一个好主意吗?如果不是因为这个原因,Racket允许绑定无效?使用
      let
      方法有两个问题:1.它不是例外afe,因此如果从包装函数中抛出异常,计数器将不会递减。2.它不处理多个值。
      void
      是单个值,您可以很好地引用它的值,但如果函数返回零(或两个或更多)值,您的代码无法正确处理。谢谢您的回答。出于好奇,如果我继续引入新的let绑定解决方案,我会遇到什么问题?另外,创建一个绑定到一个空值是一个好主意吗?如果不是,为什么Racket允许绑定到空值?let有两个问题e> 方法:1.它不是异常安全的,因此如果从包装函数中抛出异常,计数器不会递减。2.它不处理多个值。
      void
      是单个值,您可以很好地引用它的值,但如果函数返回零(或两个或更多)值,您的代码将无法正确处理。您描述的内容似乎与类似。但可能不是您所需要的。或者