how from cancel and clojure

clojure - from - ¿Cuál es la diferencia entre defn y defmacro?



save and exit vim linux (5)

Las otras respuestas lo cubren bien en profundidad, así que trataré de cubrirlo tan sucintamente como pueda. Agradecería las ediciones / comentarios sobre cómo escribirlo de manera más sucinta a la vez que lo mantengo claro:

  • una función transforma valores en otros valores .
    (reduce + (map inc [1 2 3])) => 9

  • una macro transforma el código en otro código .
    (-> xabc) => (c (b (ax))))

¿Cuál es la diferencia entre defn y defmacro? ¿Cuál es la diferencia entre una función y una macro?


Sin sonar sarcástico, uno crea una función , mientras que el otro crea una macro . En Common Lisp (y supondré que esto también se aplica a clojure), las macros se expanden antes de la compilación real de funciones. Entonces, perezoso-gato:

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

En realidad se expandirá a

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

de los cuales lazy-seq se ampliará aún más a

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

todo antes del procesamiento real de los datos pasados ​​a ellos.

Hay una historia muy linda que ayuda a explicar la diferencia (seguida de algunos ejemplos informativos) en Practical Common Lisp: Capítulo 8


Una macro es como tener un aprendiz de programador al que puedes escribir notas:

A veces, si intento depurar algo, me gusta cambiar algo así como

(* 3 2)

En algo como esto:

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

Lo cual funciona de la misma manera, excepto que imprime la expresión que acaba de evaluar y su valor, y también devuelve el valor como resultado de la expresión completa. Esto significa que puedo dejar mi código sin perturbaciones mientras examino los valores intermedios.

Esto puede ser muy útil, pero lleva mucho tiempo y es propenso a errores. ¡Puede imaginarse delegar tales tareas a su aprendiz!

En lugar de contratar a un aprendiz, puede programar el compilador para que haga estas cosas por usted.

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

Ahora intenta:

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

En realidad, hace la transformación textual en el código para usted, aunque es una computadora, elige nombres ilegibles para sus variables en lugar de la "a" que habría elegido.

Podemos preguntarle qué haría por una expresión dada:

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

Y esta es su respuesta, para que pueda ver que realmente está reescribiendo el código para usted:

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

Intenta escribir una función dbgf que haga lo mismo, y tendrás problemas, porque (dbgf (* 3 2)) -> (dbgf 6) antes de que se invoque dbgf, por lo tanto, cualquiera que sea dbgf, no se puede recuperar la expresión que necesita para imprimir.

Estoy seguro de que puede pensar en muchas formas, como la evaluación en tiempo de ejecución o pasando una cadena. Intenta escribir dbg usando defn en lugar de defmacro. Será una buena forma de convencerse a sí mismo de que las macros son buenas cosas para tener en un idioma. Una vez que lo tenga funcionando, intente usarlo en una expresión que tenga un efecto secundario y un valor, como

(dbg (print "hi"))

De hecho, las macros son tan buenas que estamos preparados para vivir con (brackety ((sintaxis))) de los LISP para poder obtenerlos. (Aunque debo decir que me gusta por sí mismo (pero luego (soy) soy un poco raro (en la cabeza)).

C también tiene macros, que funcionan más o menos de la misma manera, pero siempre van mal, y para hacerlo bien necesitas poner tantos corchetes en tu programa que parece LISP.

En realidad, se recomienda no utilizar las macros de C porque son muy propensas a errores, aunque las he visto con gran efecto por personas que realmente sabían lo que estaban haciendo.

Las macros LISP son tan efectivas que el lenguaje mismo se construye a partir de ellas, como notará si mira los archivos fuente Clojure que están escritos en Clojure.

El lenguaje base es muy simple, por lo que es fácil de implementar, y luego la compleja superestructura se construye usando macros.

Espero que esto ayude. Es bastante más larga que mis respuestas habituales, porque has hecho una pregunta profunda. Buena suerte.


defn define una función, y defmacro define una macro.
Macro es como una función pero trata su argumento (si son expresiones como datos), luego los procesa y devuelve datos (lista de símbolos que son el código) y luego evalúa ese código de retorno. Entonces es reemplazar un código por otro (en tiempo de compilación).


defn define una función, defmacro define una macro.

La diferencia entre funciones y macros es que en una llamada de función primero se evalúan los argumentos de la función, luego el cuerpo de la función se evalúa usando los argumentos.

Las macros, por otro lado, describen una transformación de un código a otro. Cualquier evaluación tiene lugar después de la transformación.

Esto significa que los argumentos pueden evaluarse varias veces o no. Como un ejemplo or es una macro. Si el primer argumento de or es falso, el segundo argumento nunca será evaluado. Si or fuera una función, esto no sería posible, porque los argumentos siempre se evaluarían antes de que se ejecute la función.

Otra consecuencia de esto es que los argumentos de una macro no necesitan ser una expresión válida antes de que la macro se expanda. Por ejemplo, podría definir una macro mymacro tal que (mymacro (12 23 +)) expande a (+ 23 12) , por lo que esto funcionará aunque (12 23 +) por sí solo no tenga sentido. No podría hacer eso con una función porque (12 23 +) se evaluaría y provocaría un error antes de que se ejecute la función.

Un pequeño ejemplo para ilustrar la diferencia:

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

La macro obtiene twice la lista (println "foo") como argumento. Luego lo transforma en la lista (do (println "foo") (println "foo")) . Este nuevo código es lo que se ejecuta.

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

Aquí println "foo" se evalúa de inmediato. Como println devuelve nil , se println dos veces con nil como argumento. twice ahora produce la lista (do nil nil) y la devuelve como resultado. Tenga en cuenta que aquí (do nil nil) no se evalúa como código, simplemente se trata como una lista.