I macro sono essenziali per fornire l'accesso alle funzionalità del linguaggio. Ad esempio, in TXR Lisp, ho una singola funzione chiamata sys:capture-cont
per l'acquisizione di una continuazione delimitata. Ma questo è scomodo da usare da solo. Quindi ci sono macro attorno ad esso, come ad esempio suspend
o obtain
and yield
che forniscono modelli alternativi per l'esecuzione sospesa, riprendibile. Sono implementati here.
Un altro esempio è la macro complessa defstruct
che fornisce la sintassi per la definizione di un tipo di struttura. Compila i suoi argomenti in lambda
-s e altro materiale che viene passato alla funzione make-struct-type
. Se i programmi utilizzati make-struct-type
direttamente per la definizione delle strutture OOP, sarebbero brutti:
1> (macroexpand '(defstruct foo bar x y (z 9) (:init (self) (setf self.x 42))))
(sys:make-struct-type 'foo 'bar '()
'(x y z)()
(lambda (#:g0101)
(let ((#:g0102 (struct-type #:g0101)))
(unless (static-slot-p #:g0102 'z)
(slotset #:g0101 'z
9)))
(let ((self #:g0101))
(setf (qref self x)
42)))
())
Yikes! C'è molto da fare che deve essere giusto. Ad esempio, non inseriamo semplicemente uno nello slot z
perché (a causa dell'ereditarietà) potremmo effettivamente essere la struttura di base di una struttura derivata e nella struttura derivata, z
potrebbe essere uno slot statico (condiviso da istanze). Sarebbe in conflitto il valore impostato per z
nella classe derivata.
In ANSI Common Lisp, un bell'esempio di macro è loop
, che fornisce un'intera sotto lingua per l'iterazione parallela. Una singola chiamata loop
può esprimere un intero complesso algoritmo.
I macro ci permettono di pensare in modo indipendente alla sintassi che vorremmo in una lingua, e le funzioni sottostanti o gli operatori speciali richiesti per implementarla. A prescindere dalle scelte che facciamo in questi due, i macro li colpiranno per noi. Non devo preoccuparmi che il make-struct
sia brutto da usare, quindi posso concentrarmi sugli aspetti tecnici; So che la macro può sembrare la stessa indipendentemente da come faccio vari compromessi. Ho preso la decisione progettuale che tutte le inizializzazioni di struct verranno eseguite da alcune funzioni registrate nel tipo. Ok, ciò significa che la mia macro deve prendere tutte le inizializzazioni nella sintassi di definizione degli slot e compilare le funzioni anonime, dove l'inizializzazione dello slot viene eseguita dal codice generato nei corpi.
Le macro sono compilatori per bit di sintassi, per cui le funzioni e gli operatori speciali sono la lingua di destinazione.
A volte le persone (persone non-Lisp, di solito) criticano le macro in questo modo: i macro non aggiungono alcuna funzionalità, ma solo zucchero sintattico.
In primo luogo, lo zucchero sintattico è una capacità.
In secondo luogo, è necessario considerare le macro da una "prospettiva totale degli hacker": combinando macro con il lavoro a livello di implementazione. Se aggiungo funzionalità a un dialetto Lisp, come strutture o continuazioni, in realtà sto estendendo la potenza. Il coinvolgimento di macro in quell'impresa è essenziale.Anche se i macro non sono la fonte della nuova potenza (non proviene dalle macro stesse), aiutano a domare e sfruttare, dandole espressione.
Se non si dispone di sys:capture-cont
, non è possibile modificare il proprio comportamento con una macro suspend
. Ma se non si dispone di macro, è necessario fare qualcosa di estremamente scomodo per fornire accesso a a una nuova funzione che non è una funzione di libreria, vale a dire hard-coding alcune nuove regole di struttura di frase in un parser.
Questa domanda ha un aspetto CW così l'ho pubblicata come tale. –
Ma che cosa sono le macro che le rendono superiori per scrivere linguaggi specifici del dominio su funzioni ordinarie? –
JtS: Avresti bisogno di molte citazioni (certamente una funzione correlata) per ritardare la valutazione di tutto, per esempio. Le macro ti permettono di evitarlo, permettendoti di fare cose arbitrarie in fase di compilazione. – Ken