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