common-lisp
Forme di raggruppamento
Ricerca…
Quando è necessario il raggruppamento?
In alcuni punti di Common Lisp, una serie di moduli viene valutata in ordine. Per esempio, nel corpo di un defun o lambda , o il corpo di un dotimes . In questi casi, la scrittura di più moduli in ordine funziona come previsto. In alcuni punti, tuttavia, come le parti then e else di un if , è consentita solo una singola forma. Naturalmente, si potrebbe voler valutare effettivamente più espressioni in quei luoghi. Per quelle situazioni, è necessario un qualche tipo di modulo implicito di raggruppamento esplicito.
progn
L'operatore di scopo generale progn viene utilizzato per valutare zero o più moduli. Viene restituito il valore dell'ultimo modulo. Ad esempio, in seguito, (print 'ciao) viene valutato (e il suo risultato viene ignorato), e poi 42 viene valutata e il suo risultato (42) viene restituito:
(progn
(print 'hello)
42)
;=> 42
Se non ci sono moduli all'interno del progn , allora viene restituito nil :
(progn)
;=> NIL
Oltre a raggruppare una serie di forme, progn ha anche la proprietà importante che se la forma progn è una forma di livello superiore , tutte le forme al suo interno vengono elaborate come forme di livello superiore. Questo può essere importante quando si scrivono macro che si espandono in più moduli che dovrebbero essere elaborati come moduli di livello superiore.
Progn è anche prezioso in quanto restituisce tutti i valori dell'ultima forma. Per esempio,
(progn
(print 'hello)
(values 1 2 3))
;;=> 1, 2, 3
Al contrario, alcune espressioni di raggruppamento restituiscono solo il valore primario della forma che produce i risultati.
Principi impliciti
Alcune forme usano programmi impliciti per descrivere il loro comportamento. Per esempio, le macro when e unless , che sono essenzialmente unilaterali se form, descrivono il loro comportamento in termini di un progn implicito . Ciò significa che una forma come
(when (foo-p foo)
form1
form2)
viene valutato e la condizione (foo-p pippo) è vera, quindi form1 e form2 sono raggruppati come se fossero contenuti all'interno di un progn . L'espansione del quando la macro è essenzialmente:
(if (foo-p foo)
(progn
form1
form2)
nil)
Prog1 e Prog2
Spesso, è utile valutare più espressioni e restituire il risultato dal primo o dal secondo modulo piuttosto che dall'ultimo. Questo è facile da realizzare usando let e, ad esempio:
(let ((form1-result form1))
form2
form3
;; ...
form-n-1
form-n
form1-result)
Poiché questa forma è comune in alcune applicazioni, Common Lisp include prog1 e prog2 che sono come progn , ma restituiscono rispettivamente il risultato della prima e della seconda forma. Per esempio:
(prog1
42
(print 'hello)
(print 'goodbye))
;; => 42
(prog2
(print 'hello)
42
(print 'goodbye))
;; => 42
Un'importante distinzione tra prog1 / prog2 e progn , tuttavia, è che progn restituisce tutti i valori dell'ultima forma, mentre prog1 e prog2 restituiscono solo il valore primario della prima e della seconda forma. Per esempio:
(progn
(print 'hello)
(values 1 2 3))
;;=> 1, 2, 3
(prog1
(values 1 2 3)
(print 'hello))
;;=> 1 ; not 1, 2, 3
Per valori multipli con la valutazione di stile prog1 , utilizzare invece il valore multiplo-prog1 . Non esiste un prog2 a valore multiplo simile, ma non è difficile da implementare se ne hai bisogno.
Bloccare
Il blocco operatore speciale consente il raggruppamento di diversi moduli Lisp (come un progn
implicito) e prende anche un nome per denominare il blocco. Quando vengono valutati i moduli all'interno del blocco, è possibile utilizzare l'operatore speciale return-from per uscire dal blocco. Per esempio:
(block foo
(print 'hello) ; evaluated
(return-from foo)
(print 'goodbye)) ; not evaluated
;;=> NIL
return-from può anche essere fornito con un valore di ritorno:
(block foo
(print 'hello) ; evaluated
(return-from foo 42)
(print 'goodbye)) ; not evaluated
;;=> 42
I blocchi denominati sono utili quando un blocco di codice ha un nome significativo o quando i blocchi sono nidificati. In alcuni contesti, è importante solo la possibilità di tornare presto da un blocco. In tal caso, è possibile utilizzare nil come nome del blocco e restituire . Il ritorno è come return-from , tranne che il nome del blocco è sempre nullo .
Nota: i moduli allegati non sono moduli di livello superiore. Questo è diverso dal progn
, dove le forme racchiuse di una forma progn
livello progn
sono ancora considerate forme di alto livello .
Tagbody
Per un sacco di controllo in una forma di gruppo, l'operatore speciale tagbody può essere molto utile. I moduli all'interno di un modulo di tagbody sono o tag go (che sono solo simboli o numeri interi) o moduli da eseguire. All'interno di un tagbody , l'operatore speciale go viene utilizzato per trasferire l'esecuzione in una nuova posizione. Questo tipo di programmazione può essere considerato piuttosto di basso livello, in quanto consente percorsi di esecuzione arbitrari. Di seguito è riportato un esempio dettagliato di come potrebbe apparire un ciclo for quando implementato come tagbody :
(let (x) ; for (x = 0; x < 5; x++) { print(hello); }
(tagbody
(setq x 0)
prologue
(unless (< x 5)
(go end))
begin
(print (list 'hello x))
epilogue
(incf x)
(go prologue)
end))
Mentre tagbody e go non sono comunemente usati, forse a causa di "GOTO considerato dannoso", ma possono essere utili quando si implementano strutture di controllo complesse come le macchine a stati. Molti costrutti di iterazione si espandono anche in un tagbody implicito . Ad esempio, il corpo di un punto è specificato come una serie di tag e forme.
Quale forma usare?
Quando si scrivono macro che si espandono in moduli che potrebbero comportare il raggruppamento, vale la pena dedicare del tempo a considerare la struttura di raggruppamento da espandere.
Per le forme di stile definizione, per esempio, un define-widget di macro che di solito appaiono come una forma di alto livello, e che molti defun s, defstruct s, ecc, di solito ha senso utilizzare un progn, in modo che i form figlio sono elaborati come moduli di livello superiore. Per i moduli di iterazione, un tagbody implicito è più comune.
Ad esempio, il corpo di dotimes , DOLIST , e fare ogni espandersi in un tagbody implicito.
Per i moduli che definiscono un "blocco" di codice denominato, è spesso utile un blocco implicito. Per esempio, mentre il corpo di un defun si trova all'interno di un progn implicito, quel progn implicito si trova all'interno di un blocco che condivide il nome della funzione. Ciò significa che return-from può essere utilizzato per uscire dalla funzione. Tale comp