我在this teaching text on Clojure尝试问题4 并且对我的计划结果感到困惑:
(def logging-enabled true)
(defmacro log
"uses a var, logging-enabled, to determine whether or not to print an expression to the console at compile time. If logging-enabled is false, (log :hi) should macroexpand to nil. If logging-enabled is true, (log :hi) should macroexpand to (prn :hi)."
[expr]
(if logging-enabled
`(prn ~expr)
nil
))
当我尝试测试我的程序时,评估下面的每个表单
(let [logging-enabled true]
(log "hi there"))
(let [logging-enabled false]
(log "hi there"))
(let [logging-enabled true]
(macroexpand (log "hi there")))
导致REPL回复完全相同的回复:
user=>
"hi there"
nil
user=>
hi there
表示宏返回了true
分支
它的if
形式。 nil
将是封闭的返回值
let
表格。
但问题是:为什么"hi there"
会被打印出来
在我的第二个测试表单中,logging-enabled
已限定为false
以上?
而且:为什么macroexpand
不会扩展我的宏而只是简单地做
和它上面的两个测试形式一样吗?
答案 0 :(得分:2)
宏在编译时扩展,因此当您的宏运行时logging-enabled
引用var logging-enabled
,而不是封闭let绑定的logging-enabled
。您需要在返回的表单中包含if
条件并引用它以防止名称被解析:
(defmacro log
[expr]
`(if ~'logging-enabled
(prn ~expr)))
答案 1 :(得分:1)
让我们看一下问题4的文本(强调增加):
编写一个宏
log
,使用varlogging-enabled
来确定是否在编译时将表达式打印到控制台 。如果logging-enabled
为false,(log :hi)
应该宏扩展到nil
。如果logging-enabled
为真,(log :hi)
应该宏扩展到(prn :hi)
。为什么要在编译期间进行此检查,而不是在运行程序时?你会失去什么?
您的宏符合此规范。我们可以通过以下实验来证实这一点:
;; With logging-enabled false
(def logging-enabled false)
(macroexpand '(log :hi))
;;=> nil
;; With logging-enabled true
(def logging-enabled true)
(macroexpand '(log :hi))
;;=> (clojure.core/prn :hi)
请注意我们传递给macroexpand
的内容:'(log :hi)
。 '
是quote
的读者快捷方式。所以这相当于(quote (log :hi))
:
(read-string "'(log :hi)")
;;=> (quote (log :hi))
这很重要,因为macroexpand
是一个函数,所以在调用macroexpand
之前评估它的参数。
你会失去什么?
我认为这是在编译时做得太多的好例子。通过在编译时执行检查,您可以在运行时不执行检查,从而节省一些时间。 但是,您无法动态开启和关闭日志记录 - 无论logging-enabled
设置为log
格式为macroexpand
时确定是否表格会打印或不打印。
如果在运行时执行此检查会更实际。然后我们可以在运行时启用和禁用日志记录。
虽然我们正在努力,但我们也可以将logging-enabled
设为dynamic var。这样,我们可以在特定的动态范围内策略性地启用/禁用日志记录。
(def ^:dynamic *logging-enabled* false)
按照惯例,动态变量的名称用*
增加。这不是必需的,但它确实提醒var是动态的。
现在,我们可以将log
定义为:
(defmacro log [expr]
`(when *logging-enabled*
(prn ~expr)))
`
表示quasi-/syntax-quotation。基本上,它与quote
类似,但您可以使用~
转义引号。
使用log
的定义,评估log
表单时会有一点点运行时间开销 - 查找*logging-enabled*
的(动态)值,并检查结果值。但是,如果*logging-enabled*
是false
,那就是它 - 我们不会评估传递给log
的表达式,也不会向控制台打印任何内容。因此,运行时开销很小。
我们可以像这样使用这个新版本的log
:
;; Root value for *logging-enabled* is false
(log :hi)
;;=> nil
;; dynamically bind *logging-enabled* to true
(binding [*logging-enabled* true]
(log :hi))
;; Prints ":hi"