我正在学习 Clojure 宏,并且想知道为什么我们不能仅使用函数进行元编程。
据我所知,宏和函数之间的区别在于,宏的参数不被评估,而是作为数据结构和符号传递,而返回值被评估(在调用宏的地方)。宏充当读者和评估者之间的代理,在评估发生之前以任意方式转换形式。在内部,他们可以使用所有语言功能,包括函数、特殊形式、文字、递归、其他宏等。
功能相反。参数在调用之前评估,返回值不在返回之后。但是宏和函数的镜像性质让我想知道,我们是否也可以通过引用它们的参数(形式)、转换形式、在函数内部对其求值,最后返回它的值来使用函数as宏。这在逻辑上不会产生相同的结果吗?当然,这会很不方便,但理论上,每个可能的宏都有等效的函数?
这是简单的中缀宏
(defmacro infix
"translate infix notation to clojure form"
[form]
(list (second form) (first form) (last form)))
(infix (6 + 6)) ;-> 12
这是使用函数的相同逻辑
(defn infix-fn
"infix using a function"
[form]
((eval (second form)) (eval (first form)) (eval (last form))))
(infix-fn '(6 + 6)) ;-> 12
现在,这种看法是否适用于所有情况,或者是否存在一些宏观情况也不甘示弱的极端情况?最后,宏只是函数调用的语法糖吗?
你的中缀函数除了文字之外不起作用:
(let [m 3, n 22] (infix-fn '(m + n))
CompilerException java.lang.RuntimeException:
Unable to resolve symbol: m in this context ...
这就是 @jkinski 指出的结果:当 eval
起作用时,
m
已经消失了。
宏可以做函数不能做的事情吗?是的。但如果你可以用函数来做到这一点,那么你通常应该这样做。
宏适用于
延期评估
考虑(来自 Halloway 和 Bedra 的Clojure 编程)
(defmacro unless [test then]
(list 'if (list 'not test) then)))
...
if-not
的部分克隆。让我们用它来定义
(defn safe-div [num denom]
(unless (zero? denom) (/ num denom)))
...防止被零除,返回
nil
:
(safe-div 10 0)
=> nil
如果我们尝试将其定义为函数:
(defn unless [test then]
(if (not test) then))
...然后
(safe-div 10 0)
ArithmeticException Divide by zero ...
在
then
的主体忽略它之前,将潜在结果评估为
unless
的 unless
参数。捕获表单并重新组织语法假设 Clojure 没有
case
形式。这是一个粗略的替代品:
(defmacro my-case [expr & stuff]
(let [thunk (fn [form] `(fn [] ~form))
pairs (partition 2 stuff)
default (if (-> stuff count odd?)
(-> stuff last thunk)
'(constantly nil))
[ks vs] (apply map list pairs)
the-map (zipmap ks (map thunk vs))]
(list (list the-map expr default))))
这个
拆开按键 (
ks
vs
),将后者包装为无参数 fn
当 Guido van Rossum 提议向 Python 添加案例声明时,委员会拒绝了他。所以Python没有case语句。如果 Rich 不想要
case
声明,但我想要,我可以要一个。
只是为了好玩,让我们使用宏来设计一个可以通过的if
形式的克隆。这无疑是函数式编程圈子里的陈词滥调,但却让我感到惊讶。我曾认为
if
是惰性求值的不可约原语。一个简单的方法是搭载 my-case
宏:
(defmacro if-like
([test then] `(if-like ~test ~then nil))
([test then else]
`(my-case ~test
false ~else
nil ~else
~then)))
这是冗长且缓慢的,并且它使用堆栈并丢失了
recur
,它被埋在闭包中。然而...
(defn fact [n]
(if-like (pos? n)
(* (fact (dec n)) n)
1))
(map fact (range 10))
=> (1 1 2 6 24 120 720 5040 40320 362880)
...它或多或少有效。
亲爱的读者,请指出我的代码中的任何错误。