Quanto sono utili le macro Lisp?

22

Common Lisp consente di scrivere macro che eseguono qualsiasi trasformazione sorgente desiderata.

Scheme ti offre un sistema igienico di corrispondenza dei modelli che ti consente di eseguire anche le trasformazioni. Quanto sono utili le macro in pratica? Paul Graham ha dichiarato in battere le medie che:

The source code of the Viaweb editor was probably about 20-25% macros.

Che tipo di cose le persone finiscono per fare con le macro?

    
posta compman 13.12.2011 - 22:24
fonte

6 risposte

15

Dai un'occhiata a questo post di Matthias Felleisen al LL1 discute la lista nel 2002. Suggerisce tre usi principali per le macro:

  1. Data sublanguages: I can write simple-looking expressions and create complex nested lists/arrays/tables with quote, unquote, etc. neatly dressed up with macros.
  2. Binding constructs: I can introduce new binding constructs with macros. That helps me get rid of lambdas and place things closer together that belong together.
  3. Evaluation reordering: I can introduce constructs that delay/postpone the evaluation of expressions as needed. Think of loops, new conditionals, delay/force, etc. [Note: In Haskell or any lazy language, this one is unnecessary.]
    
risposta data 13.12.2011 - 23:18
fonte
18

Per lo più uso le macro per aggiungere nuovi costrutti di linguaggio in grado di risparmiare tempo, che altrimenti richiederebbero un certo numero di codice boilerplate.

Ad esempio, recentemente mi sono ritrovato a volere un imperativo for-loop simile a C ++ / Java. Tuttavia, essendo un linguaggio funzionale, Clojure non è venuto con uno fuori dalla scatola. Quindi l'ho appena implementato come macro:

(defmacro for-loop [[sym init check change :as params] & steps]
  '(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

E ora posso fare:

 (for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

E ce l'hai - un nuovo linguaggio di compilazione per uso generico in sei righe di codice.

    
risposta data 20.02.2012 - 02:10
fonte
13

What sorts of things do people actually end up doing with macros?

Scrittura di estensioni di lingua o DSL.

Per avere un'idea di questo nelle lingue simili al Lisp, studia Racket , che ha diverse varianti linguistiche: Racket digitato, R6RS, e Datalog.

Vedi anche il linguaggio Boo, che ti dà accesso alla pipeline del compilatore allo scopo specifico di creare linguaggi specifici del dominio attraverso macro .

    
risposta data 13.12.2011 - 22:43
fonte
4

Ecco alcuni esempi:

Schema:

  • define per le definizioni di funzione. Fondamentalmente è un modo più breve per definire una funzione.
  • let per la creazione di variabili con scope lessicale.

Clojure:

  • defn , in base ai suoi documenti:

    Come (nome def (fn [params *] exprs *)) o (def     nome (fn ([params *] exprs *) +)) con qualsiasi doc-string o attrs aggiunti     ai var metadata

  • for : list comprehensions
  • defmacro : ironico?
  • defmethod , defmulti : funziona con metodi multipli
  • ns

Molte di queste macro rendono molto più facile scrivere codice a un livello più astratto. Penso che le macro siano simili, in molti modi, alla sintassi in non-Lisps.

La libreria di stampa Incanter fornisce macro per alcune complesse manipolazioni di dati.

    
risposta data 13.12.2011 - 22:44
fonte
4

I macro sono utili per incorporare alcuni modelli.

Ad esempio, Common Lisp non definisce il ciclo while ma ha do che può essere usato per definirlo.

Ecco un esempio di On Lisp .

(defmacro while (test &body body)
  '(do ()
       ((not ,test))
     ,@body))

(let ((a 0))
  (while (< a 10)
    (princ (incf a))))

Questo stamperà "12345678910" e se proverai a vedere cosa succede con macroexpand-1 :

(macroexpand-1 '(while (< a 10) (princ (incf a))))

Questo restituirà:

(DO () ((NOT (< A 10))) (PRINC (INCF A)))

Questa è una macro semplice, ma come detto prima, di solito sono abituati definire nuove lingue o DSL, ma da questo semplice esempio è possibile già prova ad immaginare cosa puoi fare con loro.

La macro loop è un buon esempio di ciò che le macro possono fare.

(loop for i from 0 to 10
      if (and (= (mod i 2) 0) i)
        collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
      with a = 2
      collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)               

Common Lisp ha un altro tipo di macro chiamata macro reader che può essere usato per modificare il modo in cui il lettore interpreta il codice, cioè è possibile usali per usare # {e #} ha delimitatori come # (e #).

    
risposta data 14.12.2011 - 18:00
fonte
2

Ecco uno che uso per il debug (in Clojure):

user=> (defmacro print-var [varname] '(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil

Ho dovuto gestire una tabella hash rollata a mano in C ++, dove il metodo get ha preso come riferimento un riferimento stringa non const, il che significa che non posso chiamarlo con un letterale. Per renderlo più facile da gestire, ho scritto qualcosa del tipo:

#define LET(name, value, body)  \
    do {                        \
        string name(value);     \
        body;                   \
        assert(name == value);  \
    } while (false)

Anche se è improbabile che qualcosa di simile a questo problema venga fuori in lisp, trovo particolarmente piacevole il fatto che si possano avere macro che non valutano i loro argomenti due volte, ad esempio introducendo un reale let- rilegatura. (Ammesso, qui avrei potuto aggirarlo).

Ricorro anche al trucco terribilmente brutto dell'intreccio di roba in un do ... while (false) tale che puoi usarlo nella parte allora di un if e avere ancora il resto della parte come previsto. Non è necessario questo in lisp, che è una funzione di macro che operano su alberi di sintassi piuttosto che su stringhe (o sequenze di token, penso, nel caso di C e C ++) che poi subiscono l'analisi.

Esistono alcune macro di threading incorporate che possono essere utilizzate per riorganizzare il codice in modo che sia più chiaro ("threading" come in "seminare il codice insieme", non parallelismo). Ad esempio:

(->> (range 6) (filter even?) (map inc) (reduce *))

Prende il primo modulo, (range 6) , e lo rende l'ultimo argomento del modulo successivo, (filter even?) , che a sua volta diventa l'ultimo argomento del modulo successivo e così via, in modo tale che il precedente venga riscritto in

(reduce * (map inc (filter even? (range 6))))

Penso che la prima sia molto più chiara: "prendi questi dati, fai questo, poi fallo, poi fai l'altro e abbiamo finito", ma questo è soggettivo; qualcosa che è oggettivamente vero è che leggi le operazioni nella sequenza in cui vengono eseguite (ignorando la pigrizia).

C'è anche una variante che inserisce il modulo precedente come primo (piuttosto che ultimo) argomento. Un caso d'uso è aritmetico:

(-> 17 (- 2) (/ 3))

Legge come "prendere 17, sottrarre 2 e dividere per 3".

Parlando di aritmetica, puoi scrivere una macro che esegue l'analisi delle notazioni infix, in modo che tu possa dire ad es. (infix (17 - 2) / 3) e sputerebbe (/ (- 17 2) 3) che ha lo svantaggio di essere meno leggibile e il vantaggio di essere un'espressione di lisp valida. Questa è la parte DSL / dati in sublocazione.

    
risposta data 23.06.2016 - 23:13
fonte

Leggi altre domande sui tag