在Clojure中,宏和函数有什么不同?

9

我刚开始学习宏,制作了一个简单的宏来返回列表中的最大数。

(defmacro macro-max [list] (apply max list))

如果我编写一个函数来实现同样的功能,它将是:

(defn  macro-max [list] (apply max list))

我只是在探索Clojure,所以并不了解太多。

对于专家来说,我可能听起来很傻,但似乎我可以定义一个函数代替宏。

4个回答

9

当你不想评估参数除非必要时,区别就体现出来了。

考虑以下例子:

(defn unless [pred body]
  (when (not pred)
    body))

这样做行不通,因为函数的参数会被急切地求值。所以 body 总是会执行,如下所示:
(unless (zero? 2)
        (prn "Not zero!"))
;; "Not zero!"

(unless (zero? 0)
        (prn "Not zero!"))
;; "Not zero!"

以上两个执行都会打印“不是零!”,这显然是错误的。

编写我们的unless实用程序的唯一方法是使用宏:

(defmacro unless [pred body]
  `(when (not ~pred)
     ~@body))

现在如果我们尝试一下,就会发现它按照预期工作:
(unless (zero? 2)
        (prn "Not zero!"))
;; "Not zero!"

(unless (zero? 0)
        (prn "Not zero!"))
;; this prints nothing

宏只在开发者决定时才评估其参数。

您会注意到宏中的一些特殊字符,例如“`”,“~”和“~@”。它们分别代表语法引用、取消引用和取消引用拼接,并在此处进行了解释。

我建议您学习这些符号。

希望对您有所帮助。


1
语法引用和取消引用功能有时在宏之外也很有用。 - Chouser

1
如果有函数可以完成任务,那么一定要使用函数。Clojure库遵循这个规则,因此可能大多数在其中定义的宏实际上无法表示为函数。以->>宏为例:
(->> 
   "abcdefghij"
   (take 5)
   (drop 3)
   (apply str))

上面的代码最终得到的是 "de"。如果使用函数来实现,由于宏的参数在单独计算时没有意义 - 当计算 (take 5) 时会引发错误,因此函数无法实现该功能。
宏的作用是在表达式被计算之前修改表达式(通过将前面的表达式插入到表达式的最后一个参数中)。这只有宏才能做到,而函数则不行。

1

宏是函数。不同之处在于评估时间(宏在宏展开时评估,这是编译的一步,而函数在运行时评估)。此外,宏作用于S表达式(因此宏有点像一个函数,在其中引用每个单独的参数,即您可以轻松地将任何宏(mac a b)替换为函数(fun'a' b)


你可以轻松地将任何宏(mac a b)替换为函数(fun 'a 'b)。但实际上并不容易。要使其正常工作,要么a)函数需要在调用者的上下文中解析符号,要么b)调用者必须在自己的上下文中eval函数的结果。b)更接近宏的实际工作方式。 - Rafał Dowgird
@RafałDowgird 我在谈论宏的作用。宏只是在S表达式上工作,这正是您可以使用“普通”函数所能做到的。当涉及到“函数与宏”时,宏的结果被插入到宏调用的位置中并不是非常重要,这是我的个人看法。 - Cubic

-6

我没有宏的经验,但它可以让你通过自己的函数扩展编译器。我不确定,但它是在运行时评估的。


如果你没有经验并且不确定,为什么还要回答呢? - Matteo Tassinari
说真的,你回答的哪一部分能够解答这个问题呢?连 1% 都不到吧? - Amogh Talpallikar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接