我正在学习Clojure宏,并想知道为什么我们不能只使用函数进行元编程。
据我所知,宏和函数之间的区别在于宏的参数没有被计算,而是作为数据结构和符号传递,而返回值被评估(在这个地方)调用宏的地方)。 Macro作为读者和评估者之间的代理,在评估发生之前以任意方式转换表单。在内部,他们可以使用所有语言功能,包括函数,特殊形式,文字,递归,其他宏等。
功能恰恰相反。在调用之前计算参数,返回值之后不返回值。但是宏和函数的镜像特性让我想知道,我们不能通过引用它们的参数(表单),转换表单,在函数内部进行评估,最后返回来使用函数作为宏。它的价值。这在逻辑上不会产生相同的结果吗?当然这会很不方便,但从理论上讲,对于每个可能的宏都有相同的功能吗?
这是简单的中缀宏
(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
现在,这种看法是否适用于所有情况,还是存在宏观无法超越的一些极端情况?最后,宏只是函数调用的语法糖吗?
答案 0 :(得分:11)
如果我在回答之前阅读这个问题会有所帮助。
除了文字之外,你的中缀功能不起作用:
(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的 Programming 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没有案例陈述。如果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)
......它或多或少都有效。
亲爱的读者,请指出我的代码中的任何错误。