在Clojure中编写日志宏

时间:2016-12-09 14:13:10

标签: clojure macros

我在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不会扩展我的宏而只是简单地做 和它上面的两个测试形式一样吗?

2 个答案:

答案 0 :(得分:2)

宏在编译时扩展,因此当您的宏运行时logging-enabled引用var logging-enabled,而不是封闭let绑定的logging-enabled。您需要在返回的表单中包含if条件并引用它以防止名称被解析:

(defmacro log
  [expr]
  `(if ~'logging-enabled
     (prn ~expr)))

答案 1 :(得分:1)

让我们看一下问题4的文本(强调增加):

  

编写一个宏log,使用var logging-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"