2010-04-01 4 views

risposta

2

Solo un'ipotesi - Lingue specifiche del dominio.

+0

Questa domanda ha un aspetto CW così l'ho pubblicata come tale. –

+1

Ma che cosa sono le macro che le rendono superiori per scrivere linguaggi specifici del dominio su funzioni ordinarie? –

+1

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

8

scegliere qualsiasi "code generation tool". Leggi i loro esempi. Questo è quello che può fare.

Tranne che non è necessario utilizzare un linguaggio di programmazione diverso, inserire qualsiasi codice di espansione macro in cui viene utilizzata la macro, eseguire un comando separato per creare o disporre di file di testo aggiuntivi sul disco rigido che sono solo di valore per il tuo compilatore.

Ad esempio, credo che leggere l'esempio Cog dovrebbe essere sufficiente per far piangere qualsiasi programmatore Lisp.

37

Trasformazioni del codice sorgente. Tutti i tipi. Esempi:

  • Nuove istruzioni di controllo: Avete bisogno di un WHILE? La tua lingua non ne ha uno? Perché aspettare che il dittatore benevolo possa aggiungerne uno il prossimo anno. Scrivilo tu stesso Tra cinque minuti.

  • Codice breve: sono necessarie venti dichiarazioni di classe che sembrano quasi identiche - solo una quantità limitata di luoghi è diversa. Scrivi un modulo macro che accetta le differenze come parametro e genera il codice sorgente per te. Vuoi cambiarlo più tardi? Cambia la macro in un posto.

  • Sostituzioni nell'albero di origine: Si desidera aggiungere codice nell'albero dei sorgenti? Una variabile dovrebbe essere davvero una chiamata di funzione? Avvolgi una macro attorno al codice che "guida" la sorgente e cambia i punti in cui trova la variabile.

  • Sintassi Postfix: Si desidera scrivere il codice in forma postfissa? Utilizzare una macro che riscriva il codice nella forma normale (prefisso in Lisp).

  • Effetti tempo di compilazione: È necessario eseguire un codice nell'ambiente del compilatore per informare l'ambiente di sviluppo sulle definizioni? Le macro possono generare codice che viene eseguito in fase di compilazione.

  • Semplificazioni/ottimizzazioni di codice in fase di compilazione: Si desidera semplificare un po 'di codice in fase di compilazione? Usa una macro che fa la semplificazione - in questo modo puoi spostare il lavoro dal runtime al tempo di compilazione, basato sui moduli di origine.

  • Generazione codice da descrizioni/configurazioni: è necessario scrivere un complesso insieme di classi. Ad esempio la tua finestra ha una classe, i sottoparti hanno classi, ci sono vincoli di spazio tra i riquadri, hai un ciclo di comando, un menu e un sacco di altre cose. Scrivi una macro che cattura la descrizione della tua finestra e dei suoi componenti e crea le classi e i comandi che guidano l'applicazione - dalla descrizione.

  • Miglioramenti della sintassi: la sintassi di alcune lingue non è molto comoda? Scrivi una macro che renda più conveniente per te, lo scrittore dell'applicazione.

  • Lingue specifiche di dominio: è necessario un linguaggio più vicino al dominio dell'applicazione? Crea i moduli linguistici necessari con un mucchio di macro.

metalinguistica astrazione

L'idea di base: tutto ciò che si trova sul piano linguistico (nuove forme, nuova sintassi, le trasformazioni della forma, la semplificazione, supporto IDE, ...) può ora essere programmato dallo sviluppatore pezzo per pezzo - nessuna fase separata di elaborazione delle macro.

+0

Meraviglioso elenco di motivi per utilizzare le macro. Sempre un piacere leggere le tue risposte. Grazie. – gsl

2

Oltre ad estendere la sintassi del linguaggio per consentirti di esprimere te stesso in modo più chiaro, ti dà anche controllo sulla valutazione. Prova a scrivere il tuo if nella tua lingua preferita in modo che tu possa effettivamente scrivere my_if something my_then print "success" my_else print "failure" e che entrambe le valutazioni non vengano valutate. In qualsiasi linguaggio stretto senza un macro sistema sufficientemente potente, questo è impossibile. Nessun programmatore Common Lisp troverebbe il compito troppo impegnativo, però. Idem per for -loops, foreach loop, ecc. Non è possibile esprimere queste cose in C perché richiedono una speciale semantica di valutazione (le persone hanno effettivamente provato a introdurre foreach in Objective-C, ma non ha funzionato bene), ma sono quasi banale in Common Lisp a causa delle sue macro.

3

R, il linguaggio di programmazione delle statistiche standard, dispone di macro (R manual, chapter 6). È possibile utilizzarlo per implementare la funzione lm(), che analizza i dati in base a un modello specificato come codice.

Ecco come funziona: lm(Y ~ aX + b, data) proverà a trovare i parametri a e che si adattano meglio ai dati. La parte interessante è che è possibile sostituire qualsiasi equazione lineare per aX + b e continuerà a funzionare. È una caratteristica geniale per rendere più facile il calcolo delle statistiche, e funziona solo in modo così elegante perché lm() può analizzare l'equazione fornita, che è esattamente ciò che fanno le macro Lisp.

4

Qualcosa che vorresti fare in un pre-processore?

Una macro che ho scritto è per la definizione di macchine di stato per la guida di oggetti di gioco.È più facile da leggere il codice (utilizzando la macro) che è di leggere il codice generato:

(def-ai ray-ai 
    (ground 
    (let* ((o (object)) 
      (r (range o))) 
    (loop for p in *players* 
      if (line-of-sight-p o p r) 
      do (progn 
       (setf (target o) p) 
       (transit seek))))) 
    (seek 
    (let* ((o (object)) 
      (target (target o)) 
      (r (range o)) 
      (losp (line-of-sight-p o target r))) 
    (when losp 
     (let ((dir (find-direction o target))) 
     (setf (movement o) (object-speed o dir)))) 
    (unless losp 
     (transit ground))))) 

che è di lettura:

(progn 
(defclass ray-ai (ai) nil (:default-initargs :current 'ground)) 
(defmethod gen-act ((ai ray-ai) (state (eql 'ground))) 
      (macrolet ((transit (state) 
         (list 'setf (list 'current 'ai) (list 'quote state)))) 
       (flet ((object() 
         (object ai))) 
       (let* ((o (object)) (r (range o))) 
        (loop for p in *players* 
         if (line-of-sight-p o p r) 
         do (progn (setf (target o) p) (transit seek))))))) 
    (defmethod gen-act ((ai ray-ai) (state (eql 'seek))) 
      (macrolet ((transit (state) 
         (list 'setf (list 'current 'ai) (list 'quote state)))) 
       (flet ((object() 
         (object ai))) 
       (let* ((o (object)) 
         (target (target o)) 
         (r (range o)) 
         (losp (line-of-sight-p o target r))) 
        (when losp 
        (let ((dir (find-direction o target))) 
         (setf (movement o) (object-speed o dir)))) 
        (unless losp (transit ground))))))) 

Incapsulando tutta la generazione macchina dello Stato in un macro, posso anche assicurarmi che mi riferisca solo agli stati definiti e avvisa se non è questo il caso.

+8

Accoppiamento per essere un po 'più specifico sul perché "non è un buon codice"? Sono più che felice di criticare il mio codice, ma qualcosa di un po 'più costruttivo di "non va bene" sarebbe apprezzato. Sono abbastanza sicuro di avere il mio indirizzo email se la casella dei commenti è troppo piccola. – Vatine

1

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.