Cosa rende le macro Lisp così speciali?


297

Leggendo i saggi di Paul Graham sui linguaggi di programmazione, si potrebbe pensare che le macro Lisp siano l'unica strada da percorrere. Come sviluppatore impegnato, lavorando su altre piattaforme, non ho avuto il privilegio di usare le macro Lisp. Come qualcuno che vuole capire il brusio, ti preghiamo di spiegare cosa rende questa funzione così potente.

Si prega di mettere in relazione anche questo con qualcosa che comprenderei dai mondi dello sviluppo di Python, Java, C # o C.


2
A proposito, c'è un processore macro in stile LISP per C # chiamato LeMP: ecsharp.net/lemp ... JavaScript ha anche uno chiamato Sweet.js: sweetjs.org
Qwertie,

@Qwertie Al giorno d'oggi anche i dolci funzionano?
fredrik.hjarner,

Non l'ho usato ma il commit più recente è stato sei mesi fa ... abbastanza buono per me!
Qwertie,

Risposte:


308

Per fornire una risposta breve, le macro vengono utilizzate per definire le estensioni della sintassi della lingua in Common Lisp o Domain Specific Languages ​​(DSL). Queste lingue sono integrate direttamente nel codice Lisp esistente. Ora, i DSL possono avere una sintassi simile a Lisp (come Prolog Interpreter for Common Lisp di Peter Norvig ) o completamente diversa (ad esempio Infix Notation Math for Clojure).

Ecco un esempio più concreto:
Python ha una comprensione dell'elenco integrata nel linguaggio. Ciò fornisce una sintassi semplice per un caso comune. La linea

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

restituisce un elenco contenente tutti i numeri pari tra 0 e 9. In Python 1,5 giorni non vi era tale sintassi; useresti qualcosa di più simile a questo:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

Questi sono entrambi funzionalmente equivalenti. Invociamo la nostra sospensione dell'incredulità e facciamo finta che Lisp abbia una macro di loop molto limitata che fa solo iterazione e nessun modo semplice per fare l'equivalente delle comprensioni dell'elenco.

In Lisp potresti scrivere quanto segue. Devo notare che questo esempio inventato è scelto per essere identico al codice Python, non un buon esempio di codice Lisp.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

Prima di andare oltre, dovrei spiegare meglio cos'è una macro. È una trasformazione eseguita su codice per codice. Cioè, un pezzo di codice, letto dall'interprete (o dal compilatore), che accetta il codice come argomento, manipola e restituisce il risultato, che viene quindi eseguito sul posto.

Ovviamente è un sacco di battitura e i programmatori sono pigri. Quindi potremmo definire DSL per fare la comprensione della lista. In effetti, stiamo già utilizzando una macro (la macro del ciclo).

Lisp definisce un paio di forme speciali di sintassi. La virgoletta ( ') indica che il token successivo è un valore letterale. Il quasiquote o backtick ( `) indica che il token successivo è un letterale con escape. Le escape sono indicate dall'operatore virgola. Il letterale '(1 2 3)è l'equivalente di Python [1, 2, 3]. Puoi assegnarlo a un'altra variabile o usarlo sul posto. Puoi pensare `(1 2 ,x)come l'equivalente di Python [1, 2, x]dove xè una variabile precedentemente definita. Questa notazione dell'elenco fa parte della magia che va nelle macro. La seconda parte è il lettore Lisp che sostituisce in modo intelligente le macro per il codice ma che è meglio illustrato di seguito:

Quindi possiamo definire una macro chiamata lcomp(abbreviazione di comprensione dell'elenco). La sua sintassi sarà esattamente come il pitone che abbiamo usato nell'esempio [x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Ora possiamo eseguire dalla riga di comando:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

Abbastanza pulito, eh? Ora non si ferma qui. Hai un meccanismo, o un pennello, se vuoi. Puoi avere qualsiasi sintassi che potresti desiderare. Come la withsintassi di Python o C # . O la sintassi LINQ di .NET. Alla fine, questo è ciò che attrae le persone a Lisp: la massima flessibilità.


51
+1 per l'implementazione della comprensione dell'elenco in Lisp, perché perché no?
ckb,

9
In realtà @ckb LISP ha già una macro di lista nella libreria standard: (loop for x from 0 below 10 when (evenp x) collect x), altri esempi qui . Ma in effetti, il loop è "solo una macro" (in realtà l'ho implementato da zero qualche tempo fa )
Suzanne Dupéron

8
So che è abbastanza non correlato, ma mi chiedo della sintassi e di come funziona effettivamente l'analisi ... Supponiamo di chiamare lcomp in quel modo (cambiando l'elemento thirs da "for" a "azertyuiop"): (lcomp x azertyuiop x in ( intervallo 10) se (= (% x 2) 0)) la macro continuerà a funzionare come previsto? Oppure il parametro "for" viene utilizzato nel loop in modo che debba essere la stringa "for" quando viene chiamato?
Dader,

2
@dader Quindi, loop è in realtà un'altra macro e per è un simbolo speciale usato in quella macro. Avrei potuto facilmente definire la macro senza la macro del ciclo. Tuttavia, se avessi avuto, il post sarebbe stato molto più lungo. La macro del ciclo e tutta la sua sintassi è definita in CLtL .
gte525u,

3
Una cosa di cui sono confuso con le macro di altre lingue è che le loro macro sono limitate dalla sintassi della lingua host. Le macro di Lispy possono interpretare la sintassi non di Lispy. Intendo come immaginare di creare un haskell come sintassi (senza parantesi) e interpretarlo usando le macro di Lisp. È possibile e quali sono i pro / contro dell'utilizzo delle macro rispetto all'utilizzo diretto di un lexer e un parser?
CMCDragonkai,

105

Qui troverai un ampio dibattito sulla macro lisp .

Un sottoinsieme interessante di quell'articolo:

Nella maggior parte dei linguaggi di programmazione, la sintassi è complessa. Le macro devono smontare la sintassi del programma, analizzarla e rimontarla. Non hanno accesso al parser del programma, quindi devono dipendere dall'euristica e dalle ipotesi migliori. A volte la loro analisi della frequenza di taglio è sbagliata e quindi si rompono.

Ma Lisp è diverso. Macro Lisp fare avere accesso al parser, ed è davvero un semplice parser. A una macro Lisp non viene distribuita una stringa, ma un pezzo di codice sorgente precaricato sotto forma di un elenco, poiché l'origine di un programma Lisp non è una stringa; è un elenco. E i programmi Lisp sono davvero bravi a smontare le liste e rimetterle insieme. Lo fanno in modo affidabile, ogni giorno.

Ecco un esempio esteso. Lisp ha una macro, chiamata "setf", che esegue il compito. La forma più semplice di setf è

  (setf x whatever)

che imposta il valore del simbolo "x" sul valore dell'espressione "qualunque".

Lisp ha anche liste; puoi usare le funzioni "car" e "cdr" per ottenere rispettivamente il primo elemento di un elenco o il resto dell'elenco.

Ora cosa succede se si desidera sostituire il primo elemento di un elenco con un nuovo valore? Esiste una funzione standard per farlo, e incredibilmente, il suo nome è anche peggio di "auto". È "rplaca". Ma non devi ricordare "rplaca", perché puoi scrivere

  (setf (car somelist) whatever)

per impostare l'auto di somelist.

Ciò che sta realmente accadendo qui è che "setf" è una macro. Al momento della compilazione, esamina i suoi argomenti e vede che il primo ha la forma (auto QUALCOSA). Si dice "Oh, il programmatore sta cercando di impostare l'auto di qualcosa. La funzione da usare per questo è 'rplaca'." E riscrive tranquillamente il codice in atto per:

  (rplaca somelist whatever)

5
setf è una bella illustrazione del potere delle macro, grazie per averlo incluso.
Joel,

Mi piace il momento clou ... perché la fonte di un programma Lisp non è una stringa; è un elenco. ! È il motivo principale per cui la macro LISP è superiore alla maggior parte degli altri a causa delle sue parentesi?
Studente

@Student Suppongo di sì: books.google.fr/… suggerisce che hai ragione.
VonC

55

Le macro Lisp comuni estendono essenzialmente le "primitive sintattiche" del tuo codice.

Ad esempio, in C, il costrutto switch / case funziona solo con tipi integrali e se si desidera utilizzarlo per float o stringhe, si viene lasciati con istruzioni if ​​nidificate e confronti espliciti. Non è nemmeno possibile scrivere una macro C per fare il lavoro per te.

Tuttavia, poiché una macro lisp è (essenzialmente) un programma lisp che accetta frammenti di codice come input e restituisce codice per sostituire "l'invocazione" della macro, è possibile estendere il repertorio di "primitive" per quanto si desidera, di solito finendo con un programma più leggibile.

Per fare lo stesso in C, dovresti scrivere un pre-processore personalizzato che mangia la tua sorgente iniziale (non proprio-C) e sputa qualcosa che un compilatore C può capire. Non è un modo sbagliato di farlo, ma non è necessariamente il più semplice.


41

Le macro Lisp consentono di decidere quando (se non del tutto) verrà valutata una parte o un'espressione. Per fare un semplice esempio, pensa a C:

expr1 && expr2 && expr3 ...

Ciò che dice è: valuta expr1e, se fosse vero, valuta expr2, ecc.

Ora prova a trasformare questa &&funzione in una funzione ... esatto, non puoi. Chiamando qualcosa del tipo:

and(expr1, expr2, expr3)

Valuterà tutti e tre exprsprima di dare una risposta, indipendentemente dal fatto che expr1fosse falso!

Con le macro lisp puoi scrivere qualcosa come:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

ora hai una &&, che puoi chiamare proprio come una funzione e non valuterà i moduli che le passi a meno che non siano tutti veri.

Per vedere come è utile, contrasto:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

e:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

Altre cose che puoi fare con le macro sono la creazione di nuove parole chiave e / o mini-lingue (controlla la (loop ...)macro per un esempio), integrando altre lingue in lisp, ad esempio, potresti scrivere una macro che ti consente di dire qualcosa come:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

E questo non è nemmeno entrare nelle macro di Reader .

Spero che questo ti aiuti.


Penso che dovrebbe essere: "(applica &&, @ exprs); questo e potrebbe essere sbagliato!"
Svante

1
@svante: per due motivi: primo, && è una macro, non una funzione; applica funziona solo su funzioni. secondo, applica accetta un elenco di argomenti da passare, quindi desideri uno di "(funcall fn, @ exprs)", "(applica fn (elenco, @ exprs)" o "(applica fn, @ exprs nil)", non "(applica fn, @ exprs)".
Aaron,

(and ...valuterà le espressioni fino a quando non si valuterà false, si noti che avranno luogo gli effetti collaterali generati dalla falsa valutazione, verranno saltate solo le espressioni successive.
Ocodo


10

Pensa a cosa puoi fare in C o C ++ con macro e template. Sono strumenti molto utili per gestire il codice ripetitivo, ma sono limitati in modi abbastanza severi.

  • La sintassi macro / modello limitata ne limita l'utilizzo. Ad esempio, non è possibile scrivere un modello che si espande in qualcosa di diverso da una classe o una funzione. Le macro e i modelli non possono gestire facilmente i dati interni.
  • La sintassi complessa e molto irregolare di C e C ++ rende difficile scrivere macro molto generali.

Le macro Lisp e Lisp risolvono questi problemi.

  • Le macro di Lisp sono scritte in Lisp. Hai la piena potenza di Lisp per scrivere la macro.
  • Lisp ha una sintassi molto regolare.

Parla con chiunque abbia acquisito padronanza del C ++ e chiedi loro quanto tempo hanno impiegato per apprendere tutto il lavoro sui template di cui hanno bisogno per eseguire la metaprogrammazione dei template. O tutti i trucchi folli in (eccellenti) libri come Modern C ++ Design , che sono ancora difficili da eseguire il debug e (in pratica) non portatili tra compilatori del mondo reale anche se il linguaggio è stato standardizzato per un decennio. Tutto ciò si scioglie se il linguaggio che usi per la metaprogrammazione è lo stesso linguaggio che usi per programmare!


11
Bene, per essere onesti, il problema con la metaprogrammazione del modello C ++ non è che il linguaggio di metaprogrammazione è diverso , ma che è orribile - non è stato progettato tanto quanto scoperto in quella che doveva essere una funzionalità di modello molto più semplice.
Brooks Moses,

@Brooks Certo. Le funzionalità emergenti non sono sempre male. Sfortunatamente, in un linguaggio guidato da un comitato lento, è difficile risolverli quando lo sono. È un peccato che molte delle nuove utili funzioni moderne di C ++ siano scritte in una lingua che pochi riescono a leggere, e c'è un enorme divario tra il programmatore medio e il "sommo sacerdote".
Matt Curtis,

2
@downvoter: se c'è qualcosa che non va nella mia risposta, ti preghiamo di lasciare un commento in modo che tutti possano condividere le conoscenze.
Matt Curtis,

10

Una macro lisp prende un frammento di programma come input. Questo frammento di programma è rappresentato da una struttura di dati che può essere manipolata e trasformata come preferisci. Alla fine la macro genera un altro frammento di programma e questo frammento è ciò che viene eseguito in fase di esecuzione.

C # non ha una funzione macro, tuttavia sarebbe equivalente se il compilatore analizzasse il codice in un albero CodeDOM e lo passasse a un metodo, che lo trasformava in un altro CodeDOM, che viene quindi compilato in IL.

Questo potrebbe essere usato per implementare la sintassi "zucchero" come for each-statement using-clause, linq select-expressions e così via, come macro che si trasforma nel codice sottostante.

Se Java aveva macro, è possibile implementare la sintassi Linq in Java, senza che Sun debba cambiare la lingua di base.

Ecco lo pseudo-codice per come usingpotrebbe apparire una macro in stile lisp in C # per l'implementazione :

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

Ora che in realtà esiste un processore macro in stile Lisp per C #, vorrei sottolineare che una macro per usingsarebbe simile a questa ;)
Qwertie

9

Dal momento che le risposte esistenti forniscono buoni esempi concreti che spiegano cosa ottengono le macro e come, forse aiuterebbe a raccogliere insieme alcuni dei pensieri sul perché la struttura macro sia un guadagno significativo rispetto ad altre lingue ; prima da queste risposte, poi un ottimo altrove:

... in C, dovresti scrivere un pre-processore personalizzato [che probabilmente si qualificherebbe come un programma C sufficientemente complicato ] ...

- Vatine

Parla con chiunque abbia acquisito padronanza del C ++ e chiedi loro quanto tempo hanno impiegato per apprendere tutti i tipi di template necessari per eseguire la metaprogrammazione dei template [che non è ancora così potente].

- Matt Curtis

... in Java devi hackerare la tua strada con la tessitura bytecode, anche se alcuni framework come AspectJ ti permettono di farlo usando un approccio diverso, è fondamentalmente un hack.

- Miguel Ping

DOLIST è simile a Forl's foreach o Python's for. Java ha aggiunto un tipo simile di costrutto loop con il "potenziato" per loop in Java 1.5, come parte di JSR-201. Nota che differenza fanno le macro. Un programmatore Lisp che nota un modello comune nel proprio codice può scrivere una macro per darsi un'astrazione a livello di sorgente di quel modello. Un programmatore Java che nota lo stesso schema deve convincere Sun che questa particolare astrazione merita di essere aggiunta al linguaggio. Quindi Sun deve pubblicare un JSR e convocare un "gruppo di esperti" a livello industriale per eliminare tutto. Tale processo - secondo Sun - richiede in media 18 mesi. Dopodiché, tutti gli autori del compilatore devono aggiornare i propri compilatori per supportare la nuova funzionalità. E anche quando il compilatore preferito del programmatore Java supporta la nuova versione di Java, probabilmente "ancora" non possono usare la nuova funzionalità finché non sono autorizzati a interrompere la compatibilità dei sorgenti con le versioni precedenti di Java. Quindi un fastidio che i programmatori del Common Lisp possono risolvere da soli in cinque minuti affligge i programmatori Java per anni.

- Peter Seibel, in "Pratica Lisp comune"


8

Non sono sicuro di poter aggiungere alcune informazioni ai post (eccellenti) di tutti, ma ...

Le macro Lisp funzionano alla grande grazie alla natura della sintassi di Lisp.

Lisp è un linguaggio estremamente regolare (pensare a tutto è un elenco ); le macro consentono di trattare i dati e il codice come gli stessi (non sono necessari analisi delle stringhe o altri hack per modificare le espressioni lisp). Combini queste due funzionalità e hai un modo molto pulito di modificare il codice.

Modifica: Quello che stavo cercando di dire è che Lisp è omoiconico , il che significa che la struttura dei dati per un programma lisp è scritta in lisp stesso.

Quindi, finisci con un modo per creare il tuo generatore di codice in cima alla lingua usando la lingua stessa con tutta la sua potenza (ad esempio in Java devi hackerare la tua strada con la tessitura bytecode, anche se alcuni framework come AspectJ ti consentono di farlo usando un approccio diverso, è fondamentalmente un hack).

In pratica, con le macro finisci per costruire la tua mini-lingua su lisp, senza la necessità di imparare altre lingue o strumenti e con l'utilizzo della piena potenza della lingua stessa.


1
È un commento perspicace, tuttavia, l'idea che "tutto sia un elenco" può spaventare i nuovi arrivati. per capire un elenco, devi capire contro, automobili, cdr, celle. Più precisamente, Lisp è fatto S-expressions, non elenchi.
Ribamar

6

Le macro Lisp rappresentano un modello che si verifica in quasi tutti i progetti di programmazione di grandi dimensioni. Alla fine in un programma di grandi dimensioni hai una certa sezione di codice in cui ti rendi conto che sarebbe più semplice e meno soggetto a errori scrivere un programma che genera codice sorgente come testo che puoi semplicemente incollare.

In Python gli oggetti hanno due metodi __repr__e __str__. __str__è semplicemente la rappresentazione leggibile dall'uomo. __repr__restituisce una rappresentazione che è un codice Python valido, vale a dire qualcosa che può essere inserito nell'interprete come Python valido. In questo modo puoi creare piccoli frammenti di Python che generano un codice valido che può essere incollato nel tuo vero sorgente.

In Lisp l'intero processo è stato formalizzato dal sistema macro. Certo, ti consente di creare estensioni alla sintassi e fare ogni sorta di cose fantasiose, ma la sua effettiva utilità è riassunta da quanto sopra. Ovviamente aiuta che il sistema macro Lisp ti consenta di manipolare questi "frammenti" con la piena potenza dell'intera lingua.


1
Il tuo primo paragrafo è molto chiaro per un estraneo di Lisp, il che è importante.
Wildcard il

5

In breve, le macro sono trasformazioni di codice. Consentono di introdurre molti nuovi costrutti di sintassi. Ad esempio, considera LINQ in C #. In lisp, esistono estensioni di linguaggio simili implementate da macro (ad esempio, costrutto loop incorporato, iterazione). Le macro riducono significativamente la duplicazione del codice. Le macro consentono di incorporare «linguaggi piccoli» (ad esempio, dove in c # / java si userebbe xml per configurare, in lisp si può ottenere la stessa cosa con le macro). Le macro possono nascondere difficoltà nell'utilizzo delle librerie.

Ad esempio, in lisp puoi scrivere

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

e questo nasconde tutte le cose del database (transazioni, corretta chiusura della connessione, recupero dei dati, ecc.) mentre in C # questo richiede la creazione di SqlConnections, SqlCommands, l'aggiunta di SqlParameters ai comandi Sql, il looping di SqlDataReaders, la loro corretta chiusura.


3

Mentre quanto sopra spiega cosa sono le macro e hanno anche esempi interessanti, penso che la differenza chiave tra una macro e una funzione normale sia che LISP valuta tutti i parametri prima di chiamare la funzione. Con una macro è il contrario, LISP passa i parametri non valutati alla macro. Ad esempio, se si passa (+ 1 2) a una funzione, la funzione riceverà il valore 3. Se lo si passa a una macro, riceverà un Elenco (+ 1 2). Questo può essere usato per fare tutti i tipi di cose incredibilmente utili.

  • Aggiunta di una nuova struttura di controllo, ad esempio loop o decostruzione di un elenco
  • Misurare il tempo impiegato per eseguire una funzione passata. Con una funzione il parametro verrebbe valutato prima che il controllo passasse alla funzione. Con la macro, puoi collegare il codice tra l'inizio e l'arresto del cronometro. Di seguito ha lo stesso codice esatto in una macro e una funzione e l'output è molto diverso. Nota: questo è un esempio inventato e l'implementazione è stata scelta in modo che sia identico per evidenziare meglio la differenza.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0

A proposito, Scala ha aggiunto macro alla lingua. Anche se mancano della bellezza delle macro di Lisp perché il linguaggio non è omoiconico, vale sicuramente la pena esaminarlo e gli alberi di sintassi astratti che forniscono potrebbero essere più facili da usare alla fine. È troppo presto per dire quale sistema macro preferisco.
Joerg Schmuecker,

2
"LISP passa i parametri non valutati alla macro" finalmente una risposta che lo dice chiaramente. ma hai dimenticato la seconda metà di quello: e il risultato di una macro è un codice trasformato che verrà valutato intero dal sistema al posto dell'originale come se fosse lì in primo luogo (a meno che non sia di nuovo una chiamata a una macro, che anche questa volta verrà trasformata da quella macro).
Will Ness,

0

Ho preso questo dal ricettario lisp comune, ma penso che abbia spiegato perché le macro lisp sono buone in un bel modo.

"Una macro è un normale pezzo di codice Lisp che opera su un altro pezzo di codice putativo Lisp, traducendolo in (una versione più vicina a) un eseguibile Lisp. Può sembrare un po 'complicato, quindi facciamo un semplice esempio. Supponiamo che tu voglia un versione di setq che imposta due variabili sullo stesso valore, quindi se si scrive

(setq2 x y (+ z 3))

quando z=8entrambi xey sono impostati su 11. (Non riesco a pensare a nessun uso per questo, ma è solo un esempio.)

Dovrebbe essere ovvio che non possiamo definire setq2 come una funzione. Se x=50e y=-5, questa funzione riceverebbe i valori 50, -5 e 11; non avrebbe alcuna conoscenza di quali variabili avrebbero dovuto essere impostate. Quello che vogliamo davvero dire è che quando tu (il sistema Lisp) vedi (setq2 v1 v2 e), lo consideri equivalente (progn (setq v1 e) (setq v2 e)). In realtà, questo non è del tutto giusto, ma lo farà per ora. Una macro ci consente di fare esattamente questo, specificando un programma per trasformare il modello di input (setq2 v1 v2 e)"nel modello di output (progn ...)".

Se pensavi che fosse bello puoi continuare a leggere qui: http://cl-cookbook.sourceforge.net/macros.html


1
È possibile definire setq2come una funzione se xe ysono passati per riferimento. Non so se sia possibile in CL, comunque. Quindi, per qualcuno che non conosce Lisps o CL in particolare, questo non è un esempio molto illustrativo IMO
neoascetico,

Il passaggio dell'argomento CL @neoascetic è solo per valore (ecco perché ha bisogno di macro in primo luogo). alcuni valori sono comunque puntatori (come elenchi).
Will Ness,

-5

In Python hai decoratori, in pratica hai una funzione che accetta un'altra funzione come input. Puoi fare quello che vuoi: chiamare la funzione, fare qualcos'altro, racchiudere la chiamata di funzione in una versione di acquisizione delle risorse, ecc. Ma non si può dare un'occhiata all'interno di quella funzione. Supponiamo di voler renderlo più potente, supponiamo che il tuo decoratore abbia ricevuto il codice della funzione come un elenco, quindi non solo puoi eseguire la funzione così com'è ma ora puoi eseguirne parti, riordinare le linee della funzione ecc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.