Quando richiederei una Macro invece di una funzione?

8

Sono nuovo di Clojure, sono nuovo di Macro e non ho precedenti in Lisp. Ho continuato a creare il mio caso switch come il modulo e ho finito con questo:

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

e poi ho provato a creare una funzione e ho finito con questo:

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

Entrambi

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

e

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

Funziona bene.

Quale potrebbe essere lo scenario in cui avrei bisogno di una macro e una funzione non funzionerà? C'è un inconveniente nella mia funzione o implementazione di macro?

    
posta Amogh Talpallikar 28.05.2013 - 15:06
fonte

3 risposte

7

Un vantaggio delle macro è che non seguono le stesse regole di valutazione delle funzioni, dal momento che stanno solo eseguendo trasformazioni sul codice.

Un esempio di un costrutto che non è stato possibile creare semplicemente utilizzando le funzioni è la macro threading di Clojure.

Ti consente di inserire un'espressione come primo argomento in una sequenza di moduli:

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

è equivalente a

(* (+ 5 3) 4)

Non saresti in grado di creare questo tipo di costrutto usando solo funzioni, poiché prima veniva valutata l'espressione -> , il% interno% di% e%% di% sarebbe, ovvero%% di% di ricezione verrebbe

(-> 5
    3
    4)

e ha bisogno di vedere le funzioni effettive utilizzate per funzionare.

Nel tuo esempio, considera se il risultato per alcuni casi dovesse avere effetti collaterali o chiamare qualche altra funzione. Una versione di funzione non sarebbe in grado di impedire che il codice venga eseguito in una situazione in cui tale risultato non viene selezionato, mentre una macro potrebbe impedire la valutazione di quel codice a meno che non sia l'opzione adottata.

    
risposta data 29.05.2013 - 07:31
fonte
2

Un aspetto divertente delle macro è che ti danno la possibilità di espandere la sintassi del tuo lisp e aggiungere nuove funzionalità sintattiche, e questo accade solo perché gli argomenti passati a una macro verranno valutati solo in fase di esecuzione, e non a il tempo di compilare la tua macro. Ad esempio le macro del primo / ultimo thread in clojure -> ->> non valutano i loro argomenti di espressione, altrimenti questi risultati valutati non potrebbero accettare altro (diciamo (+ 3) => 3 e 3 non è una funzione che potrebbe accettare il tuo primo argomento principale in qualcosa come (-> 4 (+ 3)) ).

Ora, se mi piace questa sintassi e voglio aggiungerla alla mia implementazione Common Lisp (che non ce l'ha) posso aggiungerla definendo una macro io stesso. Qualcosa del genere:

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

Ora sarei in grado di usarli in Common Lisp nello stesso modo in 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)

Forse vuoi anche avere una nuova sintassi per la funzione range di clojure con le tue parole chiave per la tua sintassi, qualcosa del tipo:

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

può essere definito come questa macro:

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

o aggiungi qualcosa di simile a Common Lisp (è solo per esempio poiché tutti questi possono già essere fatti nelle lingue ovviamente!):

(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)
    
risposta data 05.01.2018 - 16:08
fonte
-2

Non sono esattamente sicuro di cosa vuoi fare la tua funzione di switch-case. Quale dovrebbe essere il valore di:

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

Potrebbe essere utile se hai visto l'origine delle relative funzioni / macro principali usando il repl

(clojure.repl/source case)

In generale, tuttavia, una ragione per cui potresti desiderare una macro rispetto a una funzione è quella che utilizza la macro core case

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

non stamperà ciao. Tuttavia, se case è stato definito come una funzione, stampa "Ciao" sullo schermo e l'intera espressione valuterà a "not there" . Questo perché le funzioni valutano i loro argomenti prima di ogni chiamata alla funzione.

    
risposta data 28.05.2013 - 15:51
fonte

Leggi altre domande sui tag