Clojure,宏可以做一些函数无法完成的事情吗

问题描述 投票:0回答:1

我正在学习 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

现在,这种看法是否适用于所有情况,或者是否存在一些宏观情况也不甘示弱的极端情况?最后,宏只是函数调用的语法糖吗?

function clojure functional-programming macros lisp
1个回答
15
投票
如果我在回答之前阅读问题,将会有所帮助。

你的中缀函数除了文字之外不起作用:

(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)

...它或多或少有效。

亲爱的读者,请指出我的代码中的任何错误。

© www.soinside.com 2019 - 2024. All rights reserved.