Lisp – Macros: costrutti di controllo standard – 1

lIl titolo l’ha messo Peter, io sarei più sul semplice, mica sono così smart 😉
E il titolo speciale è perché si parla di una cosa speciale del Lisp, che è tutto speciale fià di suo. Sì le macro come le ha il Lisp non le ha nessuno (non contano le imitazioni).

Spiegare cosa siano le macro non è la cosa più semplice, come gli OGM soffrono di pregiudizi ma dai, pronti? via!

È normale che con ogni linguaggio di programmazione oltre al core ci siano librerie, che implementano un po’ tutto quello che serve. Uno dei vantaggi di questo approccio è che è più facile da implementare e comprendere. E, il vantaggio reale, è estensibile, cosa che per il Common Lisp lo fanno le macro.
Abbiamo già visto brevemente che ogni macro definisce la sua propria sintassi, determinando come le s-expressions sono passate alle Lisp forms. Con le macro del core è possibile creare nuove sintassi –costrutti di controllo come when, dolist e loop e di definizione di forms come defun e defparameter— come parte della standard library invece di doverle inserire dentro il core.
Salto qui una tiratina contro la macrophobia derivata da C e dintorni 🙄 Potete sempre –volendo– leggerla di là.

when e unless

Abbiamo già visto la forma condizionale base: if:

(if condition then-form [else-form])

condition è valutata e, se non-nil, la then-form è valutata e il suo valore ritornato; altrimenti l’else-form è valutata e il suo valore ritornato. Se condition è nil e non c’è else-form il risultato di if è nil.

(if (> 2 3) "Yup" "Nope") ==> "Nope"
(if (> 2 3) "Yup")        ==> NIL
(if (> 3 2) "Yup" "Nope") ==> "Yup"

Tuttavia if non è così bella come vorremmo perché then-form e else-form sono confinate entro un unica form. Questo comporta difficoltà quando voglio effettuare più azioni, per esempio, caso di trattamento dello spam devo scrivere (semplifico qui, Peter la fa più lunga), usando progn che esegue tutte le sue form tornando il valore dell’ultima:

(if (spam-p current-message)
    (progn
      (file-in-spam-folder current-message)
      (update-spam-database current-message)))

OK, si può fare ma siccome è una cosa abbastanza comune perché non fare qualcosa? Ecco, c’è when che consente di scrivere:

(when (spam-p current-message)
  (file-in-spam-folder current-message)
  (update-spam-database current-message))

Ma se non fosse definita nella standard library si potrebbe definire when, usando la backquote già vista anticamente:

(defmacro when (condition &rest body)
  `(if ,condition (progn ,@body)))

Nota: in realtà non si può ridefinire una macro della standard library, volendo proprio usare un altro nome, p.es. my-when.

Il complemento di when è unless, che fa l’opposto, valuta il suo corpo se condition è falsa.

(defmacro unless (condition &rest body)
  `(if (not ,condition) (progn ,@body)))

OK, sono macro davvero semplici, ma fanno il loro lavoro semplificando il codice. E ci fanno vedere come anche noi possiamo crearne di simili.

cond

Un altro caso dove if è insufficiente è quando ci sono più scelte: if a fai x, altrimenti if b fai y altrimenti fai z. Si può fare, così:

(if a
    (do-x)
    (if b
       (do-y)
       (do-z)))

ma —come dire— e probabilmente devi metterci di mezzo anche progn. Ovvio quindi che ci sia una macro per la condizione multipla cond:

(cond
  (test-1 form*)
      .
      .
      .
  (test-N form*))

Ogni elemento del corpo rappresenta una delle scelte della condizione e consiste in una lista contenente una form di condizione e zero o più forms che sono valutate se la scelta è scelta (ehmm…). Le condizioni sono valutate nell’ordine in cui compaiono finché una risulta vera. A questo punto le altre forms della scelta sono valutate e l’ultima è ritornata come valore della cond. Se non ci sono forms nella scelta è questa che viene ritornata. Per convenzione l’ultima scelta rappresentante l’altrimenti finale della catena di scelte è scritta come t. Ogni valore non-nil funzionerebbe ma così si vede chiaramente quando si legge il codice.
Quindi la precedente if annidata si può riscrivere così:

(cond (a (do-x))
      (b (do-y))
      (t (do-z)))

Scrivendo condizioni per if, when, unless e cond salta fuori l’utilità degli operatori logici and, or e not.

not (è una funzione) che inverte il valore della verità, tornando t se nil e nil se t.

and e or sono macro implementate come congiunzione e disgiunzione logiche di qualsiasi numero di subforms; sono definite come short-circuit, quindi ne sono valutate solo quanto serve. Quindi and torna nil non appena vede un nil; se nessuna vale nil torna il valore dell’ultima. or invece si ferma appena una vale non-nil e ne ritorna il valore. Se nessuna è vera torna nil. Esempi:

(not nil)             ==> t
(not (= 1 1))         ==> nil
(and (= 1 2) (= 3 3)) ==> nil
(or (= 1 2) (= 3 3))  ==> t

OK, adesso i cicli — no! pausa :mrgreen:

Posta un commento o usa questo indirizzo per il trackback.

Trackback

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: