Common Lisp Macro/3

In quest’ultima puntata della miniserie daremo uno sguardo ad una serie di tipologie di macro meno comuni ma che ci permettono di costruire dei costrutti molto versatili e potenti. Cercheremo di capire infine quando è preferibile non utilizzare una macro per astrarre pattern di codice del nostro software.

Macro utilities

Vediamo infine un ultimo esempio di macro discussa pochi giorni fa sul canale IRC #lisp (irc.freenode.net), che dimostra che lavorando sul codice nella fase di macroespansione si possono costruire utility di facile utilizzo e espandibilità. L’obiettivo che si pone la macro descritta è la conversione di un oggetto da un tipo all’altro (ad esempio da una intero a una stringa). Tali conversioni avvengono nella pratica di tutti i giorni mediante funzioni definite dall’utente o fornite da librerie (es. del tipo string-to-integer oppure integer-from-string) e sovente capita spesso di innestare le chiamate di tali funzioni per passare da un tipo all’altro passando per un tipo intermedio. Grazie alle macro è possibile creare un’operatore del tipo:

(<- (totype midtype1 midtype2 ... midtypen fromtype) object)

dove totype, midtype1, midtype2, midtypen, fromtype, sono rispettivamente il tipo che si vuole ottenere, quelli intermedi e quello di object. Tale form viene espansa in una chiamata annidata:

(totype-from-midtypen (... (midtype2-from-midtype1 (midtype1-from-fromtype object))))

All’utente della macro basterà aggiungere una funzione footype-from-bartype per poter estendere la macro. Un’implementazione più sofisticata di tale operatore, che permette anche di passare parametri alle funzioni di conversione è fornita da Troels Henriksen.

Quando usare una macro

Gli usi delle macro possono essere vari, laddove l’obiettivo è quello di creare costrutti più concisi e semplici da usare, il cosiddetto zucchero sintattico. Non sempre però è necessario o opportuno ricorrere a queste. Spesso infatti è possibile ricorrere a soluzioni funzionali per ottenere le stesse funzionalità rese da una macro. È possibile proteggere dalla valutazione gli argomenti di una funzione inserendoli in una closure, operazione possibile in linguaggi dove le funzioni sono oggetti first-class come il Common Lisp. Si consideri ad esempio questa versione funzionale, con relativo utilizzo, di my-if, in cui le closure sono create con funzioni anonime senza argomenti definite da lambda (che a sua volta è una macro):

    (defun fn-my-if (test true-fun false-fun)
(if (log-somewhere test)
(funcall true-fun)
(funcall false-fun)))

(fn-my-if
(lambda () (print "Ok tutto funzione"))
(lambda () (print "Aiuto!!!!")))

ma quando è necessario anche utilizzare gli argomenti, protetti dalla valutazione, passati ad un operatore per creare nuove variabili o manipolarli in modo più complicato, allora è preferibile creare una macro.

L’utilizzo di una macro in alternativa ad una funzione è invece solitamente sconsigliato. Nonostante i vantaggi:

  • di prestazioni: nelle macro il calcolo e il consumo di stack é effettuato durante la compilazione e non durante l’esecuzione. Si veda On Lisp (pag. 185) per un esempio in cui tale vantaggio è determinante;
  • di compattezza: le regole permettono, come vedremo tra poco, magicamente di avere codice solitamente più conciso, proprio perché sono capaci di astrarre pattern comportamentali in modo molto semplice;

vi sono diversi problemi che sconsigliano l’utilizzo delle macro laddove si può usare una funzione:

  • Debugging più difficile durante l’esecuzione, in quanto si é costretti a tracciare del codice generato dal computer e non dal programmatore.
  • Minore leggibilità del sorgente, in quanto solitamente è più semplice descrivere un gruppo di operazioni con una funzione piuttosto che descrivere la costruzione di un insieme di operazioni che poi verranno eseguite.

Analogamente, è giusto considerare l’utilizzo di altre tecniche e soluzioni offerte del linguaggio per problemi specifici. La decorazione del codice, ad esempio, può avvenire anche utilizzando pattern comportamentali, che ovviamente offrono una flessibilità maggiore a costo di maggior tempo di esecuzione e maggior codice scritto.

È invece sconsigliabile chiamare durante l’esecuzione eval su liste che descrivono del codice per risolvere i problemi da affrontare con le macro, per almeno due motivi:

  1. È inefficiente: eval deve o interpretare la lista che gli si é passata durante la fase di esecuzione, o prima compilarla e poi eseguirla
  2. È meno potente in quanto l’espressione non é valutata in un ambiente lessicale ben definito. Essendo sconsigliato riferirsi a variabili esterne all’espressione da valutare senza conoscere della loro esistenza e praticamente impossibile riferirsi a queste.

Altri operatori speciali

Accanto a defmacro il programmatore Common Lisp ha a disposizione altri operatori che gli permettono di intervenire sul codice che effettivamente sarà compilato, e quindi gli permette di creare nuovi costrutti sintattici.

Compiler Macro

le macro di compilazione permettono di definire delle regole di trasformazione che o producono la form che effettivamente verrà compilata, oppure restituiscono la form originale non trasformata. Essenzialmente servono per suggerire al compilatore quali calcoli possono essere effettuati durante la compilazione piuttosto che durante l’esecuzione. Si veda questo link per maggiori dettagli.

macrolet e symbol-macrolet

macrolet permette di creare una o più regole di trasformazione, esattamente come defmacro, ma la cui validità è locale relativa ad una singola porzione di codice.

symbol-macrolet definisce con uno scope limitato una symbol macro. Mentre una macro tradizionale appare come una chiamata a funzione, le symbol macro appaiono come simboli. È quindi possibile indicare che ogni qual volta il compilatore incontra un simbolo questo venga sostituito dal risultato di una espressione. Funzionalmente appaiono equivalenti a semplici macro senza argomenti, salvo in rari casi (si veda pag. 237 e 205 di On Lisp).

define-modify-macro

L’operatore define-modify-macro permette rapidamente di costruire una serie di macro che incorporando setf, definiscono operazioni distruttive sui parametri passati.

Reader Macro

Anche durante la fase di lettura del codice sorgente è possibile intervenire eseguendo codice arbitrario, così come in fase di compilazione. È infatti possibile modificare il reader Common Lisp in modo tale da modificarne il comportamento, ovvero cambiare il modo in cui vengono interpretati i token e le form e il modo in cui questi sono trasformati in oggetti Lisp, il che quasi equivale a definire una nuova sintassi. Solitamente queste vengono utilizzate:

  • per introdurre sintassi concise ma molto idiomatiche, come, ad esempio, la libreria per utilizzare la notazione infissa, utile per esprimere operazioni aritmetiche nel modo usuale. Con questa è possibile scrivere il codice #I( x^^2 + y^^2 ), il quale viene espanso in (+ (expt x 2) (expt y 2)));
  • per costruire bridge tra linguaggi e poter sfruttare lo stile comunemente da loro utilizzato. Esempi:
    Tratto da XMLisp:
    (send-to-server <bla x="13" y="20">)

    Tratto da , CL-ObjC:

    (log-to-file [NSString initWithUTF8String: "Hello, World!"])

    Tratto da CL-SQL:

    (clsql:select 'company :where [= [slot-value 'company 'name] "Widgets Inc."]

Conclusione

Le macro sono uno strumento potente, ma la loro potenza può rivoltarsi contro l’utente in quanto puo rendere molto piu difficoltoso il debugging di eventuali errori e la comprensione del funzionamento del codice (sebbene aiuti a facilitare la lettura del codice). Il consiglio è quindi quello di utilizzare le macro laddove ne è stato appurato il bisogno, oppure in casistiche come quelle presentate, che sono diventato ormai parte dell’idioma e dello stile accettato dai programmatori Common Lisp.

Comments

  1. Come sempre un ottimo articolo. Complimenti!

  2. Come sempre hacker…

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.