2010-02-21 11 views
60

Ho sentito dire che Lisp ti permette di ridefinire la lingua stessa, e ho provato a ricercarla, ma non c'è una spiegazione chiara da nessuna parte. Qualcuno ha un semplice esempio?In che modo Lisp consente di ridefinire la lingua stessa?

+3

Quale libro su LISP hai letto che non riguardava i macro? –

+1

Molte altre domande SO su Lisp e macro coprono la stessa base: http://stackoverflow.com/questions/267862/what-makes-lisp-macros-so-special –

+11

dovrebbe essere riaperto: Lisp offre più di macro per ridefinire il linguaggio: leggi macro, funzioni di prima classe, funzioni di consulenza, protocollo meta oggetti CLOS, combinazioni di metodi CLOS e così via. –

risposta

86

Gli utenti Lisp si riferiscono a Lisp come linguaggio di programmazione programmabile. È utilizzato per il calcolo simbolico : calcolo con simboli.

I macro sono solo un modo per sfruttare il paradigma del calcolo simbolico. La visione più ampia è che Lisp fornisce semplici modi per descrivere le espressioni simboliche: termini matematici, espressioni logiche, istruzioni di iterazione, regole, descrizioni dei vincoli e altro. Le macro (trasformazioni di moduli sorgente Lisp) sono solo una applicazione del calcolo simbolico.

Ci sono alcuni aspetti: se si chiede di ridefinire il linguaggio, ridefinire rigorosamente significherebbe ridefinire un meccanismo linguistico esistente (sintassi, semantica, pragmatica). Ma c'è anche l'estensione, l'incorporamento, la rimozione delle funzionalità linguistiche.

Nella tradizione Lisp ci sono stati molti tentativi di fornire queste funzionalità. Un dialetto Lisp e una certa implementazione possono offrire solo un sottoinsieme di essi.

A pochi modi per ridefinire/modificare/estendere funzionalità fornite da importanti implementazioni Common Lisp:

  • s-espressione sintassi. La sintassi delle espressioni S non è fissa. Il lettore (la funzione READ) utilizza le cosiddette tabelle per leggere le tabelle per specificare le funzioni che verranno eseguite quando un carattere viene letto. Si può modificare e creare tabelle di lettura. Ciò consente ad esempio di modificare la sintassi di elenchi, simboli o altri oggetti dati. Si può anche introdurre una nuova sintassi per i tipi di dati nuovi o esistenti (come le tabelle hash). È anche possibile sostituire completamente la sintassi s-expression e utilizzare un diverso meccanismo di analisi. Se il nuovo parser restituisce i moduli Lisp, non sono necessari cambiamenti per l'interprete o il compilatore. Un esempio tipico è una macro di lettura che può leggere espressioni infette. All'interno di tale macro di lettura, vengono utilizzate le espressioni infette e le regole di precedenza per gli operatori. Le macro di lettura sono diverse dalle macro ordinarie: le macro di lettura funzionano a livello di carattere della sintassi dei dati Lisp.

  • sostituzione delle funzioni. Le funzioni di livello superiore sono associate ai simboli. L'utente può cambiare questa associazione. La maggior parte delle implementazioni ha un meccanismo per consentire questo anche per molte funzioni integrate. Se si desidera fornire un'alternativa alla funzione incorporata ROOM, è possibile sostituire la sua definizione. Alcune implementazioni generano un errore e offrono l'opzione di continuare con la modifica. A volte è necessario per sbloccare un pacchetto. Ciò significa che le funzioni in generale possono essere sostituite con nuove definizioni. Ci sono dei limiti. Uno è che il compilatore può inline funzioni nel codice. Per vedere un effetto, è necessario ricompilare il codice che utilizza il codice modificato.

  • avviso di funzioni. Spesso si vuole aggiungere un comportamento alle funzioni. Questo è chiamato 'consulenza' nel mondo Lisp. Molte implementazioni di Common Lisp forniranno questa possibilità.

  • pacchetti personalizzati. I pacchetti raggruppano i simboli negli spazi dei nomi. Il pacchetto COMMON-LISP è la casa di tutti i simboli che fanno parte dello standard ANSI Common Lisp. Il programmatore può creare nuovi pacchetti e importare simboli esistenti. Quindi puoi usare nei tuoi programmi un pacchetto EXTENDED-COMMON-LISP che fornisce più o diversi servizi. Semplicemente aggiungendo (IN-PACKAGE "EXTENDED-COMMON-LISP") puoi iniziare a sviluppare usando la tua versione estesa di Common Lisp. A seconda dello spazio dei nomi utilizzato, il dialetto Lisp che utilizzi può apparire leggermente o addirittura radicalmente diverso. In Genera sulla macchina Lisp ci sono diversi dialoghi Lisp affiancati in questo modo: ZetaLisp, CLtL1, ANSI Common Lisp e Symbolics Common Lisp.

  • CLOS e oggetti dinamici. Il Common Lisp Object System è dotato di modifiche integrate. Il protocollo Meta-Object estende queste capacità. CLOS stesso può essere esteso/ridefinito in CLOS. Vuoi un'eredità diversa. Scrivi un metodo. Vuoi diversi modi per archiviare le istanze. Scrivi un metodo. Le slot dovrebbero avere più informazioni. Fornire una classe per questo. CLOS stesso è progettato in modo tale da essere in grado di implementare un'intera 'regione' di diversi linguaggi di programmazione orientati agli oggetti. Esempi tipici sono l'aggiunta di elementi come prototipi, integrazione con sistemi di oggetti esterni (come Objective C), aggiunta di persistenza, ...

  • forme Lisp. L'interpretazione delle forme Lisp può essere ridefinita con macro. Una macro può analizzare il codice sorgente che racchiude e modificarlo. Esistono vari modi per controllare il processo di trasformazione. Le macro complesse utilizzano un codice walker, che comprende la sintassi dei moduli Lisp e può applicare le trasformazioni.I macro possono essere banali, ma possono anche diventare molto complessi come le macro LOOP o ITERATE. Altri esempi tipici sono macro per SQL incorporato e generazione HTML incorporata. Le macro possono anche essere utilizzate per spostare il calcolo nel tempo di compilazione. Poiché il compilatore è esso stesso un programma Lisp, è possibile eseguire calcoli arbitrari durante la compilazione. Ad esempio una macro Lisp potrebbe calcolare una versione ottimizzata di una formula se alcuni parametri sono noti durante la compilazione.

  • Simboli. Common Lisp fornisce macro di simboli. Le macro di simboli consentono di cambiare il significato dei simboli nel codice sorgente. Un esempio tipico è questo: (con-slot (foo) bar (+ foo 17)) Qui il simbolo FOO nella sorgente racchiuso con WITH-SLOTS verrà sostituito con una chiamata (barra dei valori slot 'foo).

  • ottimizzazioni, con i cosiddetti macro del compilatore è possibile fornire versioni più efficienti di alcune funzionalità. Il compilatore userà quelle macro del compilatore. Questo è un modo efficace per l'utente di programmare le ottimizzazioni.

  • Condition Handling - gestire le condizioni che risultano dall'utilizzo del linguaggio di programmazione in un determinato modo. Common Lisp fornisce un modo avanzato per gestire gli errori. Il sistema di condizioni può anche essere usato per ridefinire le caratteristiche linguistiche. Ad esempio, si potrebbero gestire errori di funzione non definiti con un meccanismo di caricamento automatico autodidatta. Invece di atterrare nel debugger quando una funzione indefinita è vista da Lisp, il gestore degli errori potrebbe provare a caricare automaticamente la funzione e riprovare l'operazione dopo aver caricato il codice necessario.

  • Variabili speciali - inserimento di associazioni di variabili nel codice esistente. Molti dialetti Lisp, come Common Lisp, forniscono variabili speciali/dinamiche. Il loro valore viene esaminato in fase di runtime nello stack. Ciò consente di allegare il codice per aggiungere associazioni variabili che influenzano il codice esistente senza modificarlo. Un tipico esempio è una variabile come * standard-output *. Si può riassociare la variabile e tutti gli output che utilizzano questa variabile durante l'ambito dinamico della nuova associazione andranno in una nuova direzione. Richard Stallman sosteneva che per lui era molto importante che fosse reso predefinito in Emacs Lisp (anche se Stallman era a conoscenza del binding lessicale in Scheme e Common Lisp).

Lisp ha queste e altre strutture, perché è stato utilizzato per implementare un sacco di diversi linguaggi e paradigmi di programmazione. Un tipico esempio è un'implementazione incorporata di un linguaggio logico, per esempio Prolog. Lisp consente di descrivere i termini di Prolog con le espressioni S e con uno speciale compilatore, i termini di Prolog possono essere compilati in codice Lisp. A volte è necessaria la solita sintassi Prolog, quindi un parser analizzerà i tipici termini di Prolog in forme Lisp, che saranno quindi compilate. Altri esempi di linguaggi incorporati sono linguaggi basati su regole, espressioni matematiche, termini SQL, assemblatore Lisp in linea, HTML, XML e molti altri.

14

Ho intenzione di eseguire il pipe in cui Scheme è diverso da Common Lisp quando si tratta di definire una nuova sintassi. Ti permette di definire modelli usando define-syntax che vengono applicati al tuo codice sorgente ovunque vengano utilizzati. Assomigliano alle funzioni, solo che vengono eseguite in fase di compilazione e trasformano l'AST.

Ecco un esempio di come let può essere definito in termini di lambda. La linea con let è il modello da abbinare e la linea con lambda è il modello di codice risultante.

(define-syntax let 
    (syntax-rules() 
    [(let ([var expr] ...) body1 body2 ...) 
    ((lambda (var ...) body1 body2 ...) expr ...)])) 

Si noti che questo NON è come la sostituzione testuale. È possibile ridefinire il numero lambda e la definizione sopra per let continuerà a funzionare, perché sta utilizzando la definizione di lambda nell'ambiente in cui è stato definito let. Fondamentalmente, è potente come i macros ma pulisce come le funzioni.

4

I macro sono la solita ragione per dirlo. L'idea è che, poiché il codice è solo una struttura dati (un albero, più o meno), puoi scrivere programmi per generare questa struttura dati. Tutto ciò che sai sulla scrittura di programmi che generano e manipolano strutture dati, quindi, aggiunge alla tua capacità di codificare espressamente.

Le macro non sono una completa ridefinizione del linguaggio, almeno per quanto ne so (in realtà sono uno Schemer, potrei sbagliarmi), perché c'è una restrizione. Una macro può solo prendere una sola sottostruttura del codice e generare un singolo sottoalbero per sostituirlo. Pertanto non è possibile scrivere macro di trasformazione dell'intero programma, per quanto figo possa essere.

Tuttavia, i macro così come sono in grado di fare ancora un sacco di cose - sicuramente più di qualsiasi altra lingua vi lascerà fare. E se si utilizza la compilazione statica, non sarebbe affatto difficile eseguire una trasformazione dell'intero programma, quindi la restrizione è meno importante di allora.

+0

che la massima "codice è dati" era vera solo nelle implementazioni precedenti forse, ma ora ci sono così tante minuzie sui pacchetti e sugli ambienti che racchiudono e cosa no, quindi non sono più solo i simboli in una lista, ma le variabili in un albero sintattico astratto - cioè * codice *. –

2

Questa risposta riguarda specificamente Common Lisp (CL in futuro), sebbene alcune parti della risposta potrebbero essere applicabili ad altre lingue della famiglia Lisp.

Dal momento che CL utilizza le espressioni S e (principalmente) si presenta come una sequenza di applicazioni di funzione, non c'è alcuna differenza evidente tra built-in e codice utente. La differenza principale è che "le cose che il linguaggio fornisce" è disponibile in un pacchetto specifico all'interno dell'ambiente di codifica.

Con un po 'di attenzione, non è difficile codificare le sostituzioni e usarle invece.

Ora, il lettore "normale" (la parte che legge il codice sorgente e lo trasforma in notazione interna) si aspetta che il codice sorgente sia in un formato piuttosto specifico (espressioni S parentesi) ma come il lettore è guidato da qualcosa chiamate "read-tables" e queste possono essere create e modificate dallo sviluppatore, è anche possibile cambiare il modo in cui dovrebbe apparire il codice sorgente.

Queste due cose dovrebbero almeno fornire alcune motivazioni sul motivo per cui Common Lisp può essere considerato un linguaggio di programmazione riprogrammabile. Non ho un semplice esempio a portata di mano, ma ho un'implementazione parziale di una traduzione di Common Lisp in svedese (creata per il 1 aprile, qualche anno fa).

2

Dall'esterno, guardando in ...

ho sempre pensato che fosse a causa Lisp fornito, al suo interno, tali operatori logici di base, atomiche che qualsiasi processo logico può essere costruito (ed è stato costruito e fornito come set di strumenti e componenti aggiuntivi) dai componenti di base.

Non è tanto che può ridefinire se stesso in quanto la sua definizione di base è così malleabile da poter assumere qualsiasi forma e che nessuna forma è presunta/presunta nella struttura.

Come metafora, se si hanno solo composti organici si fa chimica organica, se si hanno solo ossidi metallici si fa metallurgia ma se si hanno solo elementi si può fare tutto ma si hanno dei passaggi iniziali extra da completare .... la maggior parte dei quali gli altri hanno già fatto per voi ....

credo .....

2

fredda esempio a http://www.cs.colorado.edu/~ralex/papers/PDF/X-expressions.pdf

macro lettori define X-espressioni per coesistere con S-espressioni, ad esempio, ,

? (cx <circle cx="62" cy="135" r="20"/>) 
62 

plain vanilla Common Lisp a http://www.AgentSheets.com/lisp/XMLisp/XMLisp.lisp ...

(eval-when (:compile-toplevel :load-toplevel :execute) 
    (when (and (not (boundp '*Non-XMLISP-Readtable*)) (get-macro-character #\<)) 
    (warn "~%XMLisp: The current *readtable* already contains a #/< reader function: ~A" (get-macro-character #\<)))) 

... naturalmente il parser XML non è così semplice, ma agganciandolo nel lettore Lisp è.

3

Un riferimento a "struttura e interpretazione di programmi per computer" capitolo 4-5 è quello che mi mancava dalle risposte (link).

Questi capitoli guidano l'utente nella creazione di un valutatore Lisp in Lisp. Mi piace leggere perché non solo mostra come ridefinire il Lisp in un nuovo valutatore, ma ti consente anche di conoscere le specifiche del linguaggio di programmazione Lisp.

Problemi correlati