在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"