clojure - Clojure Cond 宏的工作原理
问题描述
我对 Clojure 相当满意,但一直回避宏。为了解决这个问题,我正在通读“掌握 Clojure 宏”,并且还普遍地查看了一些 Clojure 核心宏。
在阅读cond
宏时,我对实际评估的时间有点困惑。假设clauses
不是 nil 并且初始when
测试通过,然后我们评估列表调用。List 是一个函数,因此它必须首先评估它的所有参数,然后才能进入它的主体。第一个参数只是符号'if
,下一个参数是然后(first claues)
计算第一个测试,但是我发现有点令人困惑的部分是下一个(第三个)参数会发生什么。它看起来像整个表格:
(if (next clauses)
(second clauses)
(throw (IllegalArgumentException.
"cond requires an even number of forms")))
实际上是在返回最终的宏展开进行评估之前进行评估。如果这是正确的,这是否意味着对偶数个表单的测试发生在宏实际展开之前,因此可以在宏实际生成一个运行时评估列表之前出现异常?
(defmacro cond
"Takes a set of test/expr pairs. It evaluates each test one at a
time. If a test returns logical true, cond evaluates and returns
the value of the corresponding expr and doesn't evaluate any of the
other tests or exprs. (cond) returns nil."
{:added "1.0"}
[& clauses]
(when clauses
(list 'if (first clauses)
(if (next clauses)
(second clauses)
(throw (IllegalArgumentException.
"cond requires an even number of forms")))
(cons 'clojure.core/cond (next (next clauses))))))
解决方案
查看宏如何工作的最简单方法是使用clojure.core/macroexpand-1
或检查它clojure.walk/macroexpand-all
。
例如,我们可以看到如何扩展以下表单:
(cond
(pos? 1) :positive
(neg? -1) :negative)
与macroexpand-1
:
(macroexpand-1
'(cond
(pos? 1) :positive
(neg? -1) :negative))
;; => (if (pos? 1) :positive (clojure.core/cond (neg? -1) :negative))
我们可以看到,当该表单展开时,绑定clauses
到以下表达式的序列:(pos? 1)
、:positive
和。(neg? -1)
:negative
(first clauses)
将评估为(pos? 1)
,其值将用作发出的测试表达式if
。然后,宏通过检查第一个谓词是否有多个子句来检查它是否具有所需的结果表达式:(next clauses)
将评估(:positive (neg? -1) :negative)
哪个是真实的,发出的真正分支if
将获得(second clauses)
哪个是的值:positive
。
发出的 else 分支if
将得到(clojure.core/cond (neg? -1) :negative)
. 由于发出的代码将再次包含对cond
宏的调用,因此将再次调用它并再次展开。
要查看完全扩展的代码,我们可以使用clojure.walk/macroexpand-all
:
(require 'clojure.walk)
(clojure.walk/macroexpand-all
'(cond
(pos? 1) :positive
(neg? -1) :negative))
;; => (if (pos? 1) :positive (if (neg? -1) :negative nil))
如果在宏扩展期间对包含的表单clauses
进行评估,为了扩展主题,我们可以在代码中注入一些副作用:
(clojure.walk/macroexpand-all
'(cond
(do
(println "(pos? 1) evaluated!")
(pos? 1))
(do
(println ":positive evaluated1")
:positive)
(do
(println "(neg? -1) evaluated!")
(neg? -1))
(do
(println ":negative evaluated!")
:negative)))
=>
(if
(do (println "(pos? 1) evaluated!") (pos? 1))
(do (println ":positive evaluated1") :positive)
(if (do (println "(neg? -1) evaluated!") (neg? -1)) (do (println ":negative evaluated!") :negative) nil))
正如我们所见,没有执行任何副作用,因为在宏扩展期间没有评估任何子句。
我们还可以检查调用throw
是否在宏扩展期间被评估,方法是提供clauses
这将导致(if (next clauses) ...
调用的 else 分支:
(macroexpand-1 '(cond (pos? 1)))
java.lang.IllegalArgumentException: cond requires an even number of forms
在这里我们可以看到异常被抛出并且宏的宏扩展cond
没有通过返回宏扩展代码正常完成。在宏扩展期间计算表单的原因throw
是它没有被引用(例如 ``(throw ...)`)。
推荐阅读
- hibernate - 使用 postgreSQL 5.4.2 休眠尝试保存对象时出现错误
- amazon-web-services - 组织 lambda 函数
- python - Keras 中的自定义度量函数与 tensorflow 度量不起作用
- java - 在不同子模块中的活动之间共享复杂对象的实例
- java - 显示“mvn”的 VS 代码不被识别为内部或外部命令
- nginx - NetData:即使启用插件 web_log 后,netdata 在错误日志中仍显示已禁用且未显示在 UI 中
- android - LiveData 对象特定属性的两种方式数据绑定
- node.js - 对象出现在 Redux DevTools 中,但在 console.log 中为空
- jmeter - JMeter 在长场景下失败
- d3.js - D3.js - X 轴未出现在散点图上