defn和defmacro有什么区别?

时间:2010-09-08 11:51:55

标签: clojure

defn和defmacro有什么区别?函数和宏有什么区别?

5 个答案:

答案 0 :(得分:56)

defn定义了一个函数,defmacro定义了一个宏。

函数和宏之间的区别在于,在函数调用中首先计算函数的参数,然后使用参数计算函数体。

另一方面,宏描述了从一段代码到另一段代码的转换。任何评估都在转型后进行。

这意味着可以多次评估参数或根本不评估参数。例如,or是一个宏。如果or的第一个参数为false,则永远不会计算第二个参数。如果or是一个函数,那么这是不可能的,因为在函数运行之前总是会计算参数。

这样做的另一个结果是,在扩展宏之前,宏的参数不必是有效的表达式。例如,您可以定义一个宏mymacro,以便(mymacro (12 23 +))扩展为(+ 23 12),这样即使(12 23 +)本身也是无意义的,这也会有效。您无法使用函数执行此操作,因为在函数运行之前将评估(12 23 +)并导致错误。

一个小例子来说明差异:

(defmacro twice [e] `(do ~e ~e))
(twice (println "foo"))

twice将列表(println "foo")作为参数。然后它将其转换为列表(do (println "foo") (println "foo"))。这个新代码就是执行的。

(defn twice [e] `(do ~e ~e))
(twice (println "foo"))

此处println "foo"会立即进行评估。由于println返回nil,因此以nil为参数调用两次。 twice现在生成列表(do nil nil)并将其作为结果返回。请注意,此处(do nil nil)未被评估为代码,它仅被视为列表。

答案 1 :(得分:36)

其他答案涵盖了这个深度,所以我会尽量简洁地介绍它。我会很感激编辑/评论如何更清晰地写出来,同时保持清晰:

  • a 功能值转换为其他值。
    (reduce + (map inc [1 2 3])) => 9

  • a 代码转换为其他代码
    (-> x a b c) => (c (b (a x))))

答案 2 :(得分:11)

宏就像有一个学徒程序员,你可以写笔记:

有时,如果我正在尝试调试某些内容,我希望更改类似

的内容
(* 3 2)

这样的事情:

(let [a (* 3 2)] (println "dbg: (* 3 2) = " a) a)

其工作方式相同,只是它打印出表达式 刚刚评估,它的价值,以及作为结果返回的值 整个表达。这意味着我可以在检查中间值时保持代码不受干扰。

这可能非常有用,但它很耗时且容易出错。你可能会想象 将这些任务委托给你的学徒!

您可以编程编译器为您做这些事情,而不是雇用学徒。

;;debugging parts of expressions
(defmacro dbg[x] `(let [x# ~x] (println "dbg:" '~x "=" x#) x#))

现在尝试:

(* 4 (dbg (* 3 2)))

尽管如此,它实际上会对代码进行文本转换 作为一台计算机,它为其变量选择了不可读的名称,而不是我选择的“a”。

我们可以问它对给定表达式会做什么:

(macroexpand '(dbg (* 3 2)))

这是它的答案,所以你可以看到它真的在为你重写代码:

(let* [x__1698__auto__ (* 3 2)]
      (clojure.core/println "dbg:" (quote (* 3 2)) "=" x__1698__auto__)
      x__1698__auto__)

尝试编写一个执行相同操作的函数dbgf,你会遇到问题,因为(dbgf(* 3 2)) - > (dbgf 6)在调用dbgf之前,无论dbgf做什么,它都无法恢复打印出来的表达式。

我确信你可以想到很多方法,比如运行时评估或传入一个字符串。尝试使用defn而不是defmacro编写dbg。这将是一个很好的方式来说服自己,宏是一种语言中的好东西。一旦你工作了, 尝试在具有副作用和值的表达式上使用它,例如

(dbg (print "hi"))

事实上,宏已经很好了,我们已准备好接受LISP的(包围((语法)))以获得它们。 (虽然我必须说我非常喜欢它(但后来(我)有点奇怪(在脑海里))。

C也有宏,它的工作方式大致相同,但它们总是出错,为了使它们正确,你需要在程序中加入这么多括号,看起来像LISP!

你实际上建议你不要使用C的宏,因为它们容易出错,尽管我已经看到它们已经被那些真正了解它们正在做什么的人所效用。

LISP宏非常有效,语言本身就是由它们构建的,正如你会注意到你自己用Clojure编写的Clojure源文件一样。

基本语言非常简单,因此易于实现,然后使用宏构建复杂的上层结构。

我希望这会有所帮助。这比我平常的答案要长,因为你问了一个很深的问题。祝你好运。

答案 3 :(得分:3)

没有听起来讽刺,一个创建函数,而另一个创建。在Common Lisp中(我假设这也适用于clojure),在实际编译函数之前扩展宏。所以,懒猫:

(defmacro lazy-cat
  "Expands to code which yields a lazy sequence of the concatenation
  of the supplied colls. Each coll expr is not evaluated until it is
  needed.

  (lazy-cat xs ys zs) === (concat (lazy-seq xs) (lazy-seq ys) (lazy-seq zs))"
  {:added "1.0"}
  [& colls]
  `(concat ~@(map #(list `lazy-seq %) colls)))

实际上会扩展到

`(concat ~@(map #(list `lazy-seq %) colls)))

其中lazy-seq将进一步扩展为

(list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body)))

所有在实际处理传递给他们的数据之前。

有一个非常可爱的故事,有助于解释Practical Common Lisp: Chapter 8

的差异(后面是一些信息性的例子)

答案 4 :(得分:0)

defn定义一个函数,defmacro定义一个宏 宏就像一个函数但处理它的参数(如果它们是表达式作为数据),然后处理它们并返回数据(作为代码的符号列表),然后评估返回代码。所以它将一个代码替换为另一个代码(在编译时)。