¿Cuándo requeriría una macro en lugar de una función?

7

Soy nuevo en Clojure, soy nuevo en Macros y no tengo experiencia previa en Lisp. Pasé a crear mi propia forma de caja de interruptor y terminé con esto:

(defmacro switch-case [v cases default] (if (cases v) (cases v) default )) 

e intenté hacer una función y terminé con esto:

(defn fn-switch-case [v cases default] (if (cases v) (cases v) default ))

Ambos

(switch-case 5 {6 "six" 7 "seven"} "not there")

y

(fn-switch-case 5 {6 "six" 7 "seven"} "not there")

Trabaja bien.

¿Cuál podría ser el escenario donde necesitaría una macro y una función no funcionará? ¿Hay algún inconveniente en mi función o en las implementaciones de macros?

Amogh Talpallikar
fuente
2
No tengo un ejemplo específico para usted, pero la respuesta simple es que se necesitan macros para crear una nueva sintaxis (como en los lenguajes específicos de dominio). Pero, como lo ilustra su ejemplo, las funciones normales de Lisp de primera clase ya son lo suficientemente potentes como para hacer el trabajo sin requerir macros, en muchos casos. Debe favorecer las funciones ordinarias sobre las macros; solo use macros cuando una función ordinaria no funcione.
Robert Harvey
1
Ver también stackoverflow.com/questions/2561221
Robert Harvey

Respuestas:

6

Una ventaja de las macros es que no siguen las mismas reglas de evaluación que las funciones, ya que solo están realizando transformaciones en el código.

Un ejemplo de una construcción que no podría crear simplemente usando funciones es la macro de subprocesos de Clojure .

Le permite insertar una expresión como primer argumento en una secuencia de formas:

(-> 5
  (+ 3)
  (* 4))

es equivalente a

(* (+ 5 3) 4)

Usted no será capaz de crear este tipo de constructo utilizando sólo las funciones, ya que antes de que el ->se evaluó la expresión, el interior (+ 3)y (* 4)sería, por lo ->recibiría

(-> 5
    3
    4)

y necesita ver las funciones reales que se utilizan para funcionar.

En su ejemplo, considere si el resultado para algún caso fue tener efectos secundarios o llamar a alguna otra función. Una versión de función no podría evitar que ese código se ejecute en una situación en la que no se selecciona ese resultado, mientras que una macro podría evitar que se evalúe ese código a menos que sea la opción tomada.

Justin
fuente
1
Lo entiendo. Debido a mi mente, no puedo pensar más allá de una función. Siempre termino haciendo una construcción que parece una función y se puede hacer con una función. porque estoy acostumbrado a escribir código sin hacer lenguajes y construcciones o DSls.
Amogh Talpallikar
2

Un aspecto divertido de las macros es que le brindan la posibilidad de expandir la sintaxis de su lisp y agregarle nuevas características sintácticas, y esto sucede solo porque los argumentos pasados ​​a una macro se evaluarán solo en tiempo de ejecución, y no en el momento de la ejecución. compilando tu macro. Como ejemplo, las macros de primer / último hilo en clojure -> ->>no evalúan sus argumentos de expresión, de lo contrario, estos resultados evaluados no podrían aceptar nada más (por ejemplo, (+ 3) => 3y 3no es una función que podría aceptar su primer argumento principal en algo así (-> 4 (+ 3))).

Ahora, si me gusta esta sintaxis y quiero agregarla a mi implementación de Common Lisp (que no la tiene), puedo agregarla definiendo una macro yo mismo. Algo como esto:

;;; in SBCL

;;; first thread:
(defmacro -> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,x ,@(rest m)) then `(,(first m) ,n ,@(rest m))
     finally (return n)))

;;; last thread:
(defmacro ->> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,@(rest m) ,x) then `(,(first m) ,@(rest m) ,n)
     finally (return n)))

Ahora podría usarlos en Common Lisp de la misma manera que en Clojure:

(-> #'+
    (mapcar '(2 3 4) '(1 2 3))) ;; => (3 5 7)

(->> #'>
 (sort '(9 8 3 5 7 2 4))) ;; => (9 8 7 5 4 3 2)

Además, quizás desee tener una nueva sintaxis para la rangefunción de clojure con sus propias palabras clave para su sintaxis, algo así como:

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9)

(from 0 to 10 by 0.5) ;;=> (0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5)

se puede definir como esta macro:

(defmacro from
  "Just another range!"
  [x y z & more]
  `(when (= '~y '~'to)
     (if '~more (range ~x ~z (second '~more))
         (range ~x ~z))))

o agregue algo similar a Common Lisp (¡es solo por ejemplo, ya que todo esto ya se puede hacer en los idiomas, por supuesto!):

(defmacro from (x y z &rest r)
  `(when (eql ',y 'to)
     (if (and ',r (eql (first ',r) 'by))
     (loop for i from ,x to ,z by (second ',r) collect i)
     (loop for i from ,x to ,z collect i))))

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9 10)
(from 0 to 5 by 1/2) ;=> (0 1/2 1 3/2 2 5/2 3 7/2 4 9/2 5)

fuente
-2

No estoy exactamente seguro de qué quiere que haga su función de cambio de mayúsculas y minúsculas. ¿Cuál debería ser el valor de:

(def x 5)
(switch-case x {5 :here}
             :not-there)

Puede ser útil si vio la fuente de las funciones / macros centrales relacionadas mediante el uso de la respuesta

(clojure.repl/source case)

Pero, en general, una razón por la que puede desear una macro en lugar de una función es que usar la casemacro núcleo

(case 6
  5 (print "hello")
  "not there")

no imprimirá hola Sin embargo, si casese define como una función, imprime "hola" en la pantalla y la expresión completa se evaluaría "not there". Esto se debe a que las funciones evalúan sus argumentos antes de cada llamada a la función.

WuHoUnited
fuente
3
Esto realmente no responde a la pregunta principal. Solo da vueltas en el ejemplo.
Florian Margaine
1
La última parte responde a la pregunta. Dice algo que una macro puede hacer que una función no puede hacer.
WuHoUnited
Podría haber sido un poco más claro acerca de cómo la evaluación de argumentos difiere con las macros.
Robert Harvey
Estaba aprendiendo macros. Quería crear una construcción asumiendo que no estaba allí. Así que pensé en hacer un caso de cambio usando el formulario if. No podía pensar en otra cosa.
Amogh Talpallikar