2010-12-30 10 views
6

Recentemente ho avuto una conversazione con un collega e ho provato a parlargli della bellezza del Lisp (comune). Ho cercato di spiegare le macro in qualche modo, dal momento che considero i macro una delle caratteristiche killer di Lisp, ma ho fallito piuttosto miseramente - Non ho trovato un buon esempio che sarebbe breve, conciso e comprensibile da un programmatore "semplice mortale" (decennio di esperienza Java, un ragazzo brillante, ma poca esperienza con le lingue di "ordine superiore").Esiste un semplice esempio per spiegare le macro Lisp a un programmatore "generico"?

Come spieghereste le macro Lisp con l'esempio se necessario?

+0

Correlato: http://stackoverflow.com/questions/267862/what-makes-lisp-macros-so-special – jball

+3

Penso che una delle migliori introduzioni alle macro sia in [Practical Common Lisp] (http: // gigamonkeys.com/book/practical-a-simple-database.html). È uno dei primi capitoli, quindi non assume troppo, e non è così lungo. L'intero libro è fantastico, altamente raccomandato per chiunque, programmatore "generico" o meno (ci sono anche capitoli sulla programmazione generica!). – spacemanaki

risposta

6

Nuova WHILE

vostro progettista lingua dimenticato l'istruzione while. L'hai spedito più volte. Nessun successo. Hai aspettato dalla versione 2.5, dalla 2.6 alla 3.0. Non è successo niente ...

In Lisp:

(defmacro mentre ... inserisci la tua implementazione mentre qui ...)

Fatto.

L'implementazione banale utilizzando LOOP richiede un minuto.

generazione di codice da specifiche

quindi si consiglia di analizzare cartellino di traffico (CDR). Hai i nomi dei record con le descrizioni dei campi. Ora posso scrivere classi e metodi per ognuno di questi. Potrei anche inventare qualche formato di configurazione, analizzare un file di configurazione e creare le classi. In Lisp vorrei scrivere una macro che genera il codice da una descrizione compatta.

Vedere Domain Specific Languages in Lisp, uno screencast che mostra un tipico ciclo di sviluppo da uno schizzo di lavoro a una semplice generalizzazione basata su macro.

Codice riscrittura

Immaginate di avere per accedere slot di oggetti utilizzando funzioni getter. Ora immagina di aver bisogno di accedere ad alcuni oggetti più volte in qualche area del codice. Per qualche ragione l'uso di variabili temporanee non è una soluzione.

Ora è possibile scrivere una macro WITH-GETTER che introduce un simbolo per l'espressione getter.

(with-getters (database (last-user database-last-user)) 
    ... 
    ... last-user 
    ...) 

La macro dovrebbe riscrivere la sorgente all'interno del blocco incluso e sostituire tutti i simboli specificati con l'espressione getter.

+0

+1 per _forgot_. :-) (Anche il resto della risposta è buono, ma mi è piaciuto particolarmente.) –

1

Non conosco abbastanza bene il CL, ma lo faranno i macro Scheme? Ecco un ciclo while in Scheme:

(define-syntax while 
    (syntax-rules() 
    ((while pred body ...) 
    (let loop() 
     (if pred (begin body ... (loop))))))) 

In questo caso, l'esempio dimostra che si può facilmente scrivere le proprie strutture di controllo utilizzando le macro. foof-loop è una raccolta di costrutti di loop ancora più utili (probabilmente nulla di nuovo dato a quelli di CL, ma comunque valido per una dimostrazione).


Un altro caso d'uso: prelevare valori da elenchi associativi. Dite che gli utenti passano in un elenco come opzioni per la vostra funzione. Si può facilmente scegliere i valori con l'ausilio di questa macro:

(define-syntax let-assq 
    (syntax-rules() 
    ((let-assq alist (key) body ...) 
    (let ((key (assq-ref alist 'key))) 
     body ...)) 
    ((let-assq alist (key rest ...) body ...) 
    (let ((key (assq-ref alist 'key))) 
     (let-assq alist (rest ...) body ...))))) 

;; Guile built-in 
(define (assq-ref alist key) 
    (cond ((assq key alist) => cdr) 
     (else #f))) 

Esempio di utilizzo:

(define (binary-search tree needle (lt? <)) 
    (let loop ((node tree)) 
    (and node 
     (let-assq node (value left right) 
      (cond ((lt? needle value) (loop left)) 
       ((lt? value needle) (loop right)) 
       (else value)))))) 

Notate come la macro let-assq permette individuando le chiavi value, left, e right dal "nodo", senza dover scrivere un modulo molto più lungo let.

7

Dalla mia esperienza, le macro danno la migliore impressione alle persone quando vedono come aiuta a produrre codice, che non può essere fatto dalle procedure o da altri costrutti. Molto spesso queste cose possono essere descritti come:

<common code> 
<specific code> 
<other common code> 

dove <common code> è sempre lo stesso. Ecco alcuni esempi di questo schema:

1. La macro time. codice in un linguaggio senza macro sarà simile a questo:

int startTime = getCurrentTime(); 
<actual code> 
int endTime = getCurrentTime(); 
int runningTime = endTime - startTime; 

Non si può mettere tutto il codice comune a procedimento, dal momento che avvolge codice vero e proprio giro. (OK, puoi eseguire una procedura e passare il codice effettivo nella funzione lambda, se la lingua lo supporta, ma non è sempre conveniente).
E, come probabilmente sapete, in Lisp basta creare time macro e passare codice vero e proprio ad esso:

(time 
    <actual code>) 

2. transazioni. Chiedere al programmatore Java di scrivere il metodo per semplice SELECT con JDBC - occorreranno 14-17 linee e includere il codice per aprire la connessione e la transazione, per chiuderle, diverse istruzioni nidificate try-catch-finally e solo 1 o 2 righe di codice univoco.
In Lisp è sufficiente scrivere la macro with-connection e ridurre il codice a 2-3 righe.

3. Sincronizzazione. OK, Java, C# e la maggior parte dei linguaggi moderni hanno già delle istruzioni per questo, ma cosa fare se il tuo linguaggio non ha un simile costrutto? O se vuoi introdurre un nuovo tipo di sincronizzazione come le transazioni basate su STM? Ancora una volta, dovresti scrivere una classe separata per questa attività e lavorarci manualmente, cioè inserire un codice comune attorno a ogni istruzione che desideri sincronizzare.

Questi erano solo alcuni esempi. Puoi menzionare macro "non dimenticabili" come la serie with-open, quell'ambiente di pulizia e proteggerti da perdite di ricorsi, nuove macro di costrutto come cond invece di più if s e, naturalmente, non dimenticare i costrutti pigri come if, or e and, che non valutano i loro argomenti (in opposizione all'applicazione della procedura).

Alcuni programmatori possono sostenere che la loro lingua ha una tecnologia per trattare questo o quel caso (ORM, AOP, ecc.), Ma chiedete loro, sarebbero necessarie tutte queste tecnologie, se esistessero macro?

Quindi, prendendolo del tutto e rispondendo alla domanda originale su come spiegare i macro. Prendi un codice largamente usato in Java (C#, C++, ecc.), Trasformalo in Lisp e poi riscrivilo come macro.

0

ho vista macro come astrazione simile (o doppio) per le funzioni, tranne che si può scegliere quando e come valutare gli argomenti. Questo sottolinea perché i macro sono utili, proprio come le funzioni, per prevenire la duplicazione del codice e facilitare la manutenzione.

Il mio esempio preferito sono i macro anaforici. Come aif, awhile o ae:

(defmacro aif (test-form then-form &optional else-form) 
    `(let ((it ,test-form)) 
    (if it ,then-form ,else-form))) 

    (defmacro awhile (expr &body body) 
     `(do ((it ,expr ,expr)) ((not it)) 
     ,@body)) 

    (defmacro aand (&rest args) 
     (cond 
     ((null args) t) 
     ((null (cdr args)) (car args)) 
     (t `(aif ,(car args) (aand ,@(cdr args)))))) 

Queste sono molto semplici e possono risparmiare un sacco di digitazione.

0

Non è qualcosa che puoi spiegare in un breve lasso di tempo, beh, il concetto di macro può essere spiegato in una frase e un esempio come un po 'è abbastanza facile da capire, il problema è che quella persona non capirò davvero perché le macro siano una cosa carina da avere solo con esempi così banali.

3

Dal esempi concreti possono impantanarsi nei dettagli del linguaggio li stai scrivendo, prendere in considerazione una dichiarazione non cemento, ma facilmente riconoscibili:

"Sai tutto ciò che il codice boilerplate che a volte deve scrivere "Non devi mai scrivere il file standard in lisp, dal momento che puoi sempre scrivere un generatore di codice per farlo per te."

Con "boilerplate", sto pensando a implementazioni one-off dell'interfaccia in Java, costringendo costrutti impliciti in C++, scrivendo coppie get() - set(), ecc. Penso che questa strategia retorica potrebbe funzionare meglio di provare spiegare le macro direttamente in troppi dettagli, poiché probabilmente ha già molta familiarità con le varie forme di piastra, mentre non ha mai visto una macro.

-1

no, non c'è. si dovrebbe avere familiarità con la chiarezza per comprendere chiaramente i macro.

Problemi correlati