Track
/
Factor
Factor
/
Esercizi
/
Stencil dei segnali
Stencil dei segnali

Stencil dei segnali

Esercizio di apprendimento

Introduzione

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.

Che cos'è una macro

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. esegue il corpo con [ 1 + ] sullo stack,
  2. dup append costruisce la quotation [ 1 + 1 + ],
  3. inserisce il suo codice, 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.

Gli input devono essere letterali

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

La cassetta degli attrezzi

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 + ].

Perché è più semplice di quanto sembri

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.

Un parente stretto: $[ … ]

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:.

Istruzioni

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.

1. Due volte

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

2. N ripetizioni

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

3. Comporne molte

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

4. Iterazione srotolata

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.

Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Factor Exercism

Vuoi iniziare Stencil dei segnali?

Iscriviti a Exercism per imparare e padroneggiare Factor con 47 concetti163 esercizi e il mentoring di persone reali, tutto gratis.