Quando usare la macro o non usare [chiuso]


12

Quando dovrei usare la macro nel mio programma o no?

Questa domanda è ispirata da una risposta informativa di @tarsius. Credo che molti abbiano grandi esperienze da condividere con gli altri. Spero che questa domanda diventi una delle Grandi Domande soggettive e aiuti i programmatori Emacs.

Risposte:


13

Regola del pollice

Usa le macro per la sintassi , ma mai per la semantica.

Va bene usare una macro per un po 'di zucchero sintattico, ma è una cattiva idea mettere la logica in un corpo macro indipendentemente dal fatto che sia nella macro stessa o nell'espansione. Se senti la necessità di farlo, scrivi invece una funzione e una "macro di accompagnamento" per lo zucchero sintattico. In parole povere, se hai un letmacro nella tua ricerca di problemi.

Perché?

Le macro hanno molti inconvenienti:

  • Si espandono al momento della compilazione e quindi devono essere scritti con cura per mantenere la compatibilità binaria tra più versioni. Ad esempio, la rimozione di una funzione o variabile utilizzata nell'espansione di una macro interromperà il codice byte di tutti i pacchetti dipendenti che utilizzavano questa macro. In altre parole, se si modifica l'implementazione di una macro in modo incompatibile, è necessario ricompilare tutti i pacchetti dipendenti. E la compatibilità binaria non è sempre ovvia ...
  • È necessario fare attenzione a mantenere un ordine di valutazione intuitivo . È facile scrivere una macro che valuti un modulo troppo spesso, nel momento sbagliato, nel posto sbagliato, ecc ...
  • È necessario fare attenzione alla semantica dell'associazione: le macro ereditano il loro contesto di associazione dal sito di espansione , il che significa che non si conosce a priori quali variabili lessicali esistono e se anche l'associazione lessicale è disponibile. Una macro deve funzionare con entrambe le semantiche di associazione.
  • A questo proposito, devi fare attenzione quando scrivi macro che creano chiusure . Ricorda, ottieni i binding dal sito di espansione, non dalla definizione lessicale, quindi tieni d'occhio ciò che chiudi e come crei la chiusura (ricorda, non sai se il legame lessicale è attivo nel sito di espansione e se hai anche chiusure disponibili).

1
Le note sul legame lessicale contro l'espansione macro sono particolarmente interessanti; grazie lunaryorn. (Non ho mai abilitato il legame lessicale per nessun codice che ho scritto, quindi non è un conflitto a cui abbia mai pensato.)
phils

6

Dalla mia esperienza di lettura, debug e modifica del mio codice Elisp e di altri, suggerirei di evitare il più possibile le macro.

La cosa ovvia delle macro è che non valutano i loro argomenti. Ciò significa che se si dispone di un'espressione complessa racchiusa in una macro, non si ha idea se verrà valutata o meno e in quale contesto, a meno che non si cerchi la definizione di macro. Questo potrebbe non essere un grosso problema per te stesso, poiché conosci la definizione della tua macro (attualmente, probabilmente non pochi anni dopo).

Questo svantaggio non si applica alle funzioni: tutti gli argomenti vengono valutati prima della chiamata della funzione. Ciò significa che è possibile sviluppare la complessità di valutazione in una situazione di debug sconosciuta. Puoi anche saltare direttamente le definizioni delle funzioni a te sconosciute, purché restituiscano dati semplici e supponi che non tocchino lo stato globale.

Per riformularla in un altro modo, le macro diventano la parte della lingua. Se stai leggendo un codice con macro sconosciute, stai quasi leggendo un codice in una lingua che non conosci.

Eccezioni alle precedenti prenotazioni:

Le macro standard come push, pop, dolistfanno davvero molto per voi, e sono noti per altri programmatori. Fanno sostanzialmente parte delle specifiche della lingua. Ma è praticamente impossibile standardizzare la tua macro. Non solo è difficile trovare qualcosa di semplice e utile come gli esempi sopra, ma è ancora più difficile convincere ogni programmatore ad impararlo.

Inoltre, non mi dispiace per le macro come save-selected-window: memorizzano e ripristinano lo stato e valutano i loro argomenti allo stesso modo progn. Abbastanza semplice, in realtà rende il codice più semplice.

Un altro esempio di macro OK può essere roba come defhydrao use-package. Queste macro fondamentalmente vengono con il loro mini-linguaggio. E trattano ancora dati semplici o agiscono come progn. Inoltre, di solito sono ai massimi livelli, quindi non ci sono variabili nello stack di chiamate da cui dipendono gli argomenti macro, il che rende un po 'più semplice.

Un esempio di cattiva macro, secondo me, è magit-section-actione magit-section-casein Magit. Il primo era già stato rimosso, ma il secondo rimane ancora.


4

Sarò schietto: non capisco cosa significhi "usare per la sintassi, non per la semantica". Questo è il motivo per cui parte di questa risposta affronterà la risposta di Lunaryon , e l'altra parte sarà il mio tentativo di rispondere alla domanda originale.

Quando la parola "semantica" utilizzata nella programmazione, si riferisce al modo in cui l'autore della lingua ha scelto di interpretare la propria lingua. Questo in genere si riduce a una serie di formule che trasformano le regole grammaticali della lingua in alcune altre regole che si presume che il lettore abbia familiarità. Ad esempio, Plotkin nel suo libro Approccio strutturale alla semantica operativa utilizza un linguaggio logico di derivazione per spiegare a cosa viene valutata la sintassi astratta (cosa significa eseguire un programma). In altre parole, se la sintassi è ciò che costituisce il linguaggio di programmazione su un certo livello, allora deve avere una semantica, altrimenti è, beh ... non un linguaggio di programmazione.

Ma per il momento dimentichiamo le definizioni formali, dopo tutto, è importante capire l'intenzione. Quindi, sembra che Lunaryon incoraggerebbe i suoi lettori a usare le macro quando non si aggiunge alcun nuovo significato al programma, ma solo una sorta di abbreviazione. Per me questo sembra strano e in netto contrasto con il modo in cui le macro vengono effettivamente utilizzate. Di seguito sono riportati esempi che creano chiaramente nuovi significati:

  1. defun e gli amici, che sono una parte essenziale della lingua, non possono essere descritti negli stessi termini in cui descriveresti le chiamate di funzione.

  2. setfle regole che descrivono come funzionano le funzioni sono inadeguate per descrivere gli effetti di un'espressione simile (setf (aref x y) z).

  3. with-output-to-stringcambia anche la semantica del codice all'interno della macro. Allo stesso modo, with-current-buffere un sacco di altre with-macro.

  4. dotimes e simili non possono essere descritti in termini di funzioni.

  5. ignore-errors cambia la semantica del codice che avvolge.

  6. Ci sono un sacco di macro definite in cl-libpacchetto, eieiopacchetto e alcune altre librerie quasi onnipresenti utilizzate in Emacs Lisp che, sebbene possano sembrare che le forme di funzioni abbiano interpretazioni diverse.

Quindi, semmai, le macro sono lo strumento per introdurre una nuova semantica in una lingua. Non riesco a pensare a un modo alternativo di farlo (almeno non in Emacs Lisp).


Quando non userei le macro:

  1. Quando non introducono nuovi costrutti, con nuove semantiche (ovvero la funzione farebbe altrettanto bene il lavoro).

  2. Quando questa è solo una sorta di convenienza (ovvero creare una macro denominata mvbche si espande cl-multiple-value-bindsolo per renderla più breve).

  3. Quando mi aspetto che un sacco di errori nella gestione del codice vengano nascosti dalla macro (come è stato notato, le macro sono difficili da eseguire il debug).


Quando preferirei fortemente le macro rispetto alle funzioni:

  1. Quando i concetti specifici del dominio sono oscurati dalle chiamate di funzione. Ad esempio, se uno ha bisogno di passare lo stesso contextargomento a un gruppo di funzioni che devono essere chiamate in successione, preferirei avvolgerle in una macro, dove l' contextargomento è nascosto.

  2. Quando il codice generico in forma di funzione è eccessivamente dettagliato (i costrutti di iterazione nuda in Emacs Lisp sono troppo dettagliati per i miei gusti ed espongono inutilmente il programmatore ai dettagli di implementazione di basso livello).


Illustrazione

Di seguito è la versione annacquata intesa a dare un'idea dell'intuizione di ciò che si intende per semantica in informatica.

Supponiamo di avere un linguaggio di programmazione estremamente semplice con solo queste regole di sintassi:

variable := 1 | 0
operation := +
expression := variable | (expression operation expression)

con questo linguaggio possiamo costruire "programmi" come (1+0)+1o 0o ((0+0))ecc.

Ma finché non forniamo regole semantiche, questi "programmi" sono privi di significato.

Supponiamo che ora dotiamo la nostra lingua delle regole (semantiche):

0+0  0+1  1+0  1+1  (exp)
---, ---, ---, ---, -----
 0   1+0   1    0    exp

Ora possiamo effettivamente calcolare usando questa lingua. Cioè, possiamo prendere la rappresentazione sintattica, capirla in modo astratto (in altre parole, convertirla in sintassi astratta), quindi usare regole semantiche per manipolare questa rappresentazione.

Quando parliamo della famiglia di lingue Lisp, le regole semantiche di base della valutazione delle funzioni sono all'incirca quelle del lambda-calcolo:

(f x)  (lambda x y)   x
-----, ------------, ---
 fx        ^x.y       x

Citando e meccanismi macro-espansione sono anche noti come strumenti meta-programmazione, e cioè permettono uno di parlare circa il programma, invece di programmazione. Raggiungono questo obiettivo creando una nuova semantica usando il "meta layer". L'esempio di una macro così semplice è:

(a . b)
-------
(cons a b)

Questa non è in realtà una macro in Emacs Lisp, ma avrebbe potuto esserlo. Ho scelto solo per semplicità. Si noti che nessuna delle regole semantiche sopra definite si applicherebbe a questo caso poiché il candidato più vicino (f x)interpreta funa funzione, mentre anon è necessariamente una funzione.


1
"Zucchero sintattico" non è in realtà un termine in informatica. È qualcosa usato dagli ingegneri per significare varie cose. Quindi non ho una risposta definitiva. Dato che stiamo parlando di semantica, quindi, la regola per interpretare la sintassi del modulo: (x y)è approssimativamente "valutare y, quindi chiamare x con il risultato di una valutazione precedente". Quando scrivi (defun x y), y non viene valutato e nemmeno x. Il fatto che sia possibile scrivere un codice con effetto equivalente utilizzando una semantica diversa non nega che questa parte di codice abbia una semantica propria.
wvxvw,

1
Ri 'il tuo secondo commento: puoi per favore riferirmi alla definizione di macro che stai usando che dice ciò che rivendichi? Ti ho appena fatto un esempio in cui viene introdotta una nuova regola semantica mediante una macro. Almeno nel senso preciso di CS. Non penso che discutere delle definizioni sia produttivo e penso anche che le definizioni utilizzate in CS siano la migliore corrispondenza per questa discussione.
wvxvw,

1
Beh ... ti capita di avere la tua idea di cosa significhi la parola "semantica", che è un po 'ironica, se ci pensi :) Ma davvero, queste cose hanno definizioni molto precise, tu, beh ... ignorare quelli. Il libro che ho collegato nella risposta è un libro di testo standard sulla semantica come insegnato nel corso corrispondente di CS. Se hai appena letto l'articolo corrispondente del Wiki: en.wikipedia.org/wiki/Semantics_%28computer_science%29 vedrai di cosa si tratta. L'esempio è la regola per l'interpretazione (defun x y)dell'applicazione vs funzione.
wvxvw,

Vabbè, non penso che questo sia il mio modo di discutere. È stato un errore persino provare ad avviarne uno; Ho rimosso i miei commenti perché non ha senso in tutto questo. Mi dispiace di aver perso tempo.
lunaryorn,
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.