在Clojure中编写日志宏
我正在尝试第四道题 我对我的课程结果感到困惑:在Clojure中编写日志宏,clojure,macros,Clojure,Macros,我正在尝试第四道题 我对我的课程结果感到困惑: (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 l
(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
表示宏返回了
它的if
形式。nil
将是来自封闭
让
形成
但问题是:为什么当
在我的第二个测试表单中,已启用日志记录
的范围被限定为false
在上面
还有:为什么macroexpand
不是扩展我的宏,而是简单地执行
与上面的两个测试表单相同?宏是在编译时展开的,因此当宏运行时,
日志记录启用
指的是var日志记录启用
,而不是由封闭let绑定的日志记录启用
。您需要在返回的表单中包含if
条件,并引用它以防止解析名称:
(defmacro log
[expr]
`(if ~'logging-enabled
(prn ~expr)))
让我们看一看问题4的课文(增加了重点): 编写一个宏
log
,该宏使用变量logging enabled
,以确定是否在编译时将表达式打印到控制台。如果日志记录已启用
为false,(日志:hi)
应宏扩展为nil
。如果启用了日志记录
为true,(日志: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)
注意我们传递给宏扩展的内容:(日志:hi)
<代码>“
是的读卡器快捷方式。所以这相当于(quote(log:hi))
:
这一点很重要,因为macroexpand
是一个函数,所以在调用macroexpand
之前会计算它的参数
你会失去什么
我认为这是一个在编译时做得太多的好例子。通过在编译时执行检查,不在运行时执行检查可以节省一点时间但是,您失去了动态打开和关闭日志记录的能力——当特定的日志
表单被宏扩展
ed确定该表单是否将打印消息时,无论启用了什么日志记录
如果在运行时执行此检查,则更为实际。然后我们可以在运行时启用和禁用日志记录
当我们这样做的时候,我们还可以启用日志记录
a。这样,我们就可以在特定的动态范围内战略性地启用/禁用日志记录
(def ^:dynamic *logging-enabled* false)
按照惯例,动态变量的名称增加了*
s。这不是一个要求,但它确实提醒我们var是动态的
现在,我们可以将log
定义为:
(defmacro log [expr]
`(when *logging-enabled*
(prn ~expr)))
`
表示。基本上,它类似于quote
,只是您可以使用~
来转义该引号
使用log
的这个定义,评估log
表单时会有很小的运行时开销——查找*已启用日志记录*
的(动态)值,并检查结果值。但是,如果*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"
我对启用日志功能有点困惑。。。这与启用日志记录有何不同?另请注意:
macroexpand
似乎连你的新宏都没有展开-它似乎在对它求值。@TerrenceBrannon-你如何调用macroexpand
<代码>(宏扩展(日志“消息”)=>(如果启用了日志记录(clojure.core/prn“消息”))(我用)。您需要引用logging enabled
,以防止将其解析为您的ns/logging enabled
变量,因为您希望动态绑定它。您不应在此处使用~'
启用日志记录。它将扩展为非限定符号,但您确实希望它扩展为限定符号。@Lee-我没有控制台来查看历史记录,但根据这些评论,我相信我忘记了我使用的表达式前面的引号。我将修改“除非您可以使用~”来逃避引号。”还可以提到,您还可以使用~@
对引号进行转义。
;; Root value for *logging-enabled* is false
(log :hi)
;;=> nil
;; dynamically bind *logging-enabled* to true
(binding [*logging-enabled* true]
(log :hi))
;; Prints ":hi"