背景:
我打算为我正在开发的代码生成调试消息。我编写了一个宏来避免在每个函数中编写日志记录调用。我知道这限制了生成更多自定义调试消息的能力,但作为回报,它将日志记录与代码隔离开来。这就是我的目标。这种宏方法也有其他缺点,例如它只限制了对这个宏的函数绑定的创建,但我想我可以忍受它。
以下是宏的定义和演示其用法的示例。
(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)
更容易找出谁叫谁等等。我有一个想法可以做到这一点。它涉及一个跟踪缩进的变量,然后在记录函数名称之后我将增加缩进,并且在评估body之后并且在返回之前值减少该值。如下所示:
(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的绑定是正确的方法。
有没有更好的方法来实现同样的目标?如果没有,这个设计有什么问题吗?只要他们将日志记录和代码分开,我就会接受任何想法/改变。
感谢您的帮助!
答案 0 :(得分:4)
我有几个建议:
parameterize
表达式的末尾会为您恢复原始值。 / LI>
您在宏中进行的所有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 ...)
,因为在syntax-case
中(与syntax-rules
不同),后者实际上会绑定一个名为define-func
的模式变量,会影响你可能想做的任何递归宏调用(我会批准你在这里没有一个,但无论如何这都是一个好习惯。)#'(args ...)
,而是将其转换为语法对象列表,以便您可以使用identifier?
进行测试。与使用symbol?
进行测试相比,这更有意图揭示,并允许我们在同一个表达式中测试func-name
。(syntax-e #'func-name)
!请引用它。