What is the difference between defn and defmacro?

后端 未结 5 1374
青春惊慌失措
青春惊慌失措 2021-02-01 02:25

What is the difference between defn and defmacro? What is the difference between a function and a macro?

相关标签:
5条回答
  • 2021-02-01 03:21

    defn define a function, and defmacro define a macro.
    Macro is like a function but treat it's argument (if they are expressions as data), then process them and return data (list of symbols which are the code) and then evaluate that return code. So it's replace one code with another (on compile time).

    0 讨论(0)
  • 2021-02-01 03:24

    defn defines a function, defmacro defines a macro.

    The difference between functions and macros is that on a function call first the arguments of the function are evaluated then the body of the function is evaluated using the arguments.

    Macros on the other hand describe a transformation from one piece of code to another. Any evaluation takes place after the transformation.

    This means that arguments may be evaluated multiple times or not at all. As an example or is a macro. If the first argument of or is false, the second argument will never be evaluated. If or were a function, this would not be possible, because the arguments would always be evaluated before the function runs.

    Another consequence of this is that the arguments of a macro need not be a valid expression before the macro is expanded. For example you could define a macro mymacro such that (mymacro (12 23 +)) expands to (+ 23 12), so this will work even though (12 23 +) on its own would be nonsense. You couldn't do that with a function because (12 23 +) would be evaluated, and cause an error, before the function runs.

    A small example to illustrate the difference:

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

    The macro twice gets the list (println "foo") as an argument. It then transforms it into the list (do (println "foo") (println "foo")). This new code is what gets executed.

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

    Here println "foo" is evaluted right away. Since println returns nil, twice is called with nil as its argument. twice now produces the list (do nil nil) and returns that as its result. Note that here (do nil nil) is not evaluated as code, it's just treated as a list.

    0 讨论(0)
  • 2021-02-01 03:24

    The Other answers cover this well in depth so I'll try to cover it as succinctly as I can. I would appreciate edits/comments on how to write it more succinctly while keeping it clear:

    • a function transforms values into other values.
      (reduce + (map inc [1 2 3])) => 9

    • a macro transforms code into other code.
      (-> x a b c) => (c (b (a x))))

    0 讨论(0)
  • 2021-02-01 03:24

    Without sounding snarky, one creates a function, while the other creates a macro. In Common Lisp (and I'll assume this applies to clojure as well), Macros are expanded before actual compilation of functions. So, lazy-cat:

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

    Will actually be expanded to

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

    of which lazy-seq will then be further expanded to

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

    all before the actually processing of the data passed to them.

    There's a very cute story that helps explain the difference (followed by some informative examples) at Practical Common Lisp: Chapter 8

    0 讨论(0)
  • 2021-02-01 03:31

    A macro is like having an apprentice programmer that you can write notes to:

    Sometimes, if I'm trying to debug something, I like to change something like

    (* 3 2)
    

    Into something like this:

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

    Which works the same way, except that it prints out the expression it has just evaluated, and its value, as well as returning the value as the result of the whole expression. This means that I can leave my code undisturbed whilst examining intermediate values.

    This can be very useful, but it's time consuming and error prone to type. You might imagine delegating such tasks to your apprentice!

    Rather than hiring an apprentice, you can program the compiler to do these things for you.

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

    Now try:

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

    It actually makes the textual transformation on the code for you, although being a computer, it chooses unreadable names for its variables instead of the "a" I would have chosen.

    We can ask it what it would do for a given expression:

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

    And this is its answer, so you can see that it really is rewriting the code for you:

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

    Try to write a function dbgf that does the same thing, and you'll have problems, because (dbgf (* 3 2)) -> (dbgf 6) before dbgf is called, and so whatever dbgf does, it can't recover the expression that it needs to print out.

    I'm sure you can think of many ways round this, like run-time evaluation or passing in a string. Try to write dbg using defn instead of defmacro. It will be a good way to convince yourself that macros are good things to have in a language. Once you've got it working, try using it on an expression that has a side effect as well as a value, like

    (dbg (print "hi"))
    

    In fact macros are so good to have that we're prepared to live with the (brackety ((syntax))) of LISPs in order to get them. (Although I must say that I rather like it for its own sake (but then (I am) a bit weird (in the head)).

    C also has macros, which work in roughly the same way, but they're always going wrong, and to get them right you need to put so many brackets into your program that it looks like LISP!

    You're actually recommended not to use C's macros because they're so error prone, although I have seen them used to great effect by people who really knew what they were doing.

    LISP macros are so effective that the language itself is built out of them, as you'll notice if you look at the Clojure source files that are themselves written in Clojure.

    The base language is very simple so that it's easy to implement, and then the complex superstructure is built up using macros.

    I do hope this helps. It's rather longer than my usual answers, because you've asked a deep question. Good luck.

    0 讨论(0)
提交回复
热议问题