Sai già come costruire una quotation a partire da pezzi e come eseguirla. Una quotation è soltanto una sequenza di codice, quindi le parole che già conosci per le sequenze valgono anche per lei:
USING: kernel math prettyprint quotations sequences ;
{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call . ! => 9
[ 1 + ] dup append . ! => [ 1 + 1 + ]
append unisce due quotation, concat appiattisce un array di quotation, map trasforma i dati in quotation e >quotation rende il risultato chiamabile. MACRO: prende esattamente questa abilità e la sposta a tempo di compilazione.
Una definizione MACRO: somiglia a una parola qualsiasi, ma il suo corpo viene eseguito mentre il programma viene compilato, non mentre viene eseguito. Il corpo deve lasciare una sola quotation sullo stack. Il compilatore poi inserisce quella quotation nel punto di chiamata: sostituisce la chiamata alla macro con il codice prodotto dal corpo. A tempo di esecuzione non resta alcuna macro, solo il codice generato.
USING: kernel macros sequences ;
MACRO: twice ( quot -- spliced )
dup append ;
Ogni volta che scrivi [ 1 + ] twice, il compilatore:
[ 1 + ] sullo stack,dup append costruisce la quotation [ 1 + 1 + ],1 + 1 +, in quel punto.Quindi una parola che chiama twice viene compilata come se avessi scritto 1 + 1 + a mano:
: inc-twice ( x -- y ) [ 1 + ] twice ;
5 inc-twice . ! => 7
Il contrasto chiave: una parola normale che riceve una quotation la esegue con call a tempo di esecuzione. Una macro non chiama mai nulla: consegna al compilatore del codice nuovo da compilare al suo posto.
Poiché il corpo viene eseguito a tempo di compilazione, gli input di una macro devono essere noti in quel momento. Devono essere dei letterali nel punto di chiamata: una quotation [ … ], un numero, un array, una stringa. Un valore che esiste solo a tempo di esecuzione non può essere passato a una macro.
! DOCTEST: SKIP — illustrative call sites, not standalone code
[ 1 + ] twice ! ok: the quotation is a literal
[ 1 + ] 3 repeat-quot ! ok: the quotation and the number are literals
Il corpo di una macro è codice normale che restituisce una quotation. Le parole che hai già incontrato fanno quasi tutto il lavoro:
| per costruire… | usa |
|---|---|
[ A B ] da [ A ] e [ B ]
|
append |
| una quotation da un array di quotation | concat >quotation |
| una quotation per ogni elemento di una sequenza |
map, poi concat >quotation
|
Altre due parole, da sequences e kernel, completano la cassetta degli attrezzi.
<repetition> (in sequences) crea una sequenza di n copie di un unico elemento. Il suo effetto sullo stack è ( length elt -- repetition ): il conteggio viene per primo. Il risultato è una sequenza virtuale, quindi appiattiscila nel solito modo:
3 [ 1 + ] <repetition> concat >quotation . ! => [ 1 + 1 + 1 + ]
curry (in kernel) antepone un valore a una quotation, producendo una nuova quotation che inserisce quel valore per primo:
3 [ + ] curry . ! => [ 3 + ]
{ 1 2 3 } [ . ] curry . ! => [ { 1 2 3 } . ]
curry è il modo in cui incorpori un elemento specifico in una quotation: è esattamente ciò che trasforma { 10 20 30 } e [ + ] in [ 10 + ] [ 20 + ] [ 30 + ].
In alcuni linguaggi, le macro richiedono una sintassi speciale di quote/unquote e meccanismi aggiuntivi per evitare collisioni di nomi. Factor non ha bisogno di nulla di tutto ciò: una quotation è già una sequenza di parole risolte, perché il parser ha cercato ciascun dup, + e append quando ha letto il [ … ]. Quindi il corpo di una macro non è altro che la manipolazione di sequenze che già fai, che però restituisce una quotation.
$[ … ]
Una macro restituisce una quotation da inserire in una chiamata. Quando invece vuoi un semplice valore calcolato a tempo di analisi, $[ code ] esegue code mentre il file viene letto e inserisce il risultato nel sorgente come letterale:
! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;
CONSTANT: build-time $[ now ]
$[ … ] proviene dal vocabolario literals, che raccoglie le parole per interpolare nei letterali i risultati ottenuti a tempo di analisi. È menzionato per completezza. I quattro stencil che scriverai, twice, repeat-quot, compose-many ed each-literal, sono tutte dichiarazioni MACRO:.
Ti occupi del toolkit di generazione di codice di una stazione di segnali. Ogni macro è uno stencil: un piccolo template che il compilatore usa per stampare codice specializzato in ogni punto di chiamata.
Scrivi solo le quattro macro. Il file di test definisce piccole parole wrapper che chiamano le macro (una macro si può osservare solo attraverso il codice che genera), poi controlla i risultati a runtime dei wrapper. Lascia i wrapper come sono.
Per ogni attività, il corpo riceve gli input letterali del punto di chiamata sullo stack e deve lasciare una quotation. Le righe «compila in» mostrano il codice lineare che la quotation dovrebbe produrre.
twice prende una quotation letterale e restituisce una quotation che
esegue l'originale due volte di seguito.
! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ] -> must build [ 1 + 1 + ]
5 inc-twice . ! => 7
repeat-quot prende una quotation letterale e un conteggio letterale n,
e restituisce una quotation che esegue l'originale n volte di seguito.
n = 0 restituisce la quotation vuota [ ].
! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3 -> must build [ 1 + 1 + 1 + ]
5 inc-thrice . ! => 8
compose-many prende un array letterale di quotation e restituisce un'unica
quotation che esegue ciascuna a turno. Un array vuoto
restituisce la quotation vuota.
! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] } -> must build [ 1 + 2 * 1 - ]
5 pipeline . ! => 11
each-literal prende una sequenza letterale e una quotation. Restituisce
una quotation che, per ogni elemento della sequenza, impila
quell'elemento e poi esegue la quotation: un'iterazione che avviene
in fase di compilazione, quindi il codice generato è lineare.
! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ] -> must build [ 10 + 20 + 30 + ]
sum-three . ! => 60
Questa è la più strutturale delle quattro: sia la lunghezza della sequenza sia i suoi valori danno forma al codice generato. I suggerimenti ti guidano a costruirla un passo alla volta.
Iscriviti a Exercism per imparare e padroneggiare Factor con 47 concetti163 esercizi e il mentoring di persone reali, tutto gratis.