Já sabes construir uma quotation a partir de peças e executá-la. Uma quotation é apenas uma sequência de código, por isso as palavras de sequência que já conheces também se aplicam a ela:
USING: kernel math prettyprint quotations sequences ;
{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call . ! => 9
[ 1 + ] dup append . ! => [ 1 + 1 + ]
O append junta duas quotations, o concat achata um array delas, o map transforma dados em quotations e o >quotation torna o resultado passível de ser chamado. O MACRO: pega nessa mesma competência e transporta-a para o tempo de compilação.
Uma definição MACRO: parece uma palavra normal, mas o seu corpo é executado enquanto o teu programa está a ser compilado, e não enquanto é executado. O corpo tem de deixar uma quotation na pilha. O compilador insere depois essa quotation no local da chamada: substitui a chamada da macro pelo código que o corpo produziu. Em tempo de execução já não resta nenhuma macro, apenas o código gerado.
USING: kernel macros sequences ;
MACRO: twice ( quot -- spliced )
dup append ;
Onde quer que escrevas [ 1 + ] twice, o compilador:
[ 1 + ] na pilha,dup append constrói a quotation [ 1 + 1 + ],1 + 1 +, nesse local.Assim, uma palavra que chama twice compila como se tivesses escrito 1 + 1 + à mão:
: inc-twice ( x -- y ) [ 1 + ] twice ;
5 inc-twice . ! => 7
O contraste essencial: uma palavra normal que recebe uma quotation chama-a com call em tempo de execução. Uma macro nunca chama nada: entrega ao compilador código novo para compilar no seu lugar.
Como o corpo é executado em tempo de compilação, as entradas de uma macro têm de ser conhecidas nessa altura. Têm de ser literais no local da chamada: uma quotation [ … ], um número, um array, uma string. Um valor que só existe em tempo de execução não pode ser entregue a uma 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
O corpo de uma macro é código normal que devolve uma quotation. As palavras que já conheceste fazem a maior parte do trabalho:
| para construir… | usa |
|---|---|
[ A B ] a partir de [ A ] e [ B ]
|
append |
| uma quotation a partir de um array delas | concat >quotation |
| uma quotation por cada elemento de uma sequência |
map, depois concat >quotation
|
Mais duas palavras, de sequences e kernel, completam o conjunto.
<repetition> (em sequences) cria uma sequência de n cópias de um elemento. O seu efeito na pilha é ( length elt -- repetition ): a contagem vem primeiro. O resultado é uma sequência virtual, por isso achata-a da forma habitual:
3 [ 1 + ] <repetition> concat >quotation . ! => [ 1 + 1 + 1 + ]
curry (em kernel) acrescenta um valor ao início de uma quotation, produzindo uma nova quotation que empilha esse valor primeiro:
3 [ + ] curry . ! => [ 3 + ]
{ 1 2 3 } [ . ] curry . ! => [ { 1 2 3 } . ]
O curry é a forma de incorporar um elemento concreto numa quotation: é exatamente o que transforma { 10 20 30 } e [ + ] em [ 10 + ] [ 20 + ] [ 30 + ].
Nalgumas linguagens, as macros precisam de sintaxe especial de quote/unquote e de maquinaria extra para evitar colisões de nomes. O Factor não precisa de nada disso: uma quotation já é uma sequência de palavras resolvidas; o parser procurou cada dup, + e append quando leu o [ … ]. Assim, o corpo de uma macro é apenas a manipulação de sequências que já fazes, que por acaso devolve uma quotation.
$[ … ]
Uma macro devolve uma quotation para ser inserida numa chamada. Quando, em vez disso, queres um valor simples calculado em tempo de análise, $[ code ] executa code enquanto o ficheiro está a ser lido e incorpora o resultado no código-fonte como literal:
! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;
CONSTANT: build-time $[ now ]
O $[ … ] vem do vocabulário literals, que reúne as palavras para interpolar resultados de tempo de análise em literais. É mencionado por uma questão de completude. Os quatro stencils que vais escrever, twice, repeat-quot, compose-many e each-literal, são todos declarações MACRO:.
Estás a fazer a manutenção do conjunto de ferramentas de geração de código de uma estação de sinais. Cada macro é um stencil: um pequeno molde que o compilador usa para gerar código especializado em cada ponto de chamada.
Tu escreves apenas as quatro macros. O ficheiro de testes define pequenas palavras que servem de invólucro às tuas macros e que as chamam (uma macro só pode ser observada através do código que gera); depois verifica os resultados de execução desses invólucros. Deixa os invólucros como estão.
Em cada tarefa, o corpo recebe na pilha os valores literais do ponto de chamada e tem de deixar uma quotation. As linhas "compila para" mostram o código linear que a tua quotation deve produzir.
twice recebe uma quotation literal e devolve uma quotation que
executa a original duas vezes seguidas.
! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ] -> must build [ 1 + 1 + ]
5 inc-twice . ! => 7
repeat-quot recebe uma quotation literal e uma contagem literal n
e devolve uma quotation que executa a original n vezes seguidas.
n = 0 devolve a quotation vazia [ ].
! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3 -> must build [ 1 + 1 + 1 + ]
5 inc-thrice . ! => 8
compose-many recebe um array literal de quotations e devolve uma
única quotation que executa cada uma por sua vez. Um array vazio
devolve a quotation vazia.
! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] } -> must build [ 1 + 2 * 1 - ]
5 pipeline . ! => 11
each-literal recebe uma sequência literal e uma quotation. Devolve
uma quotation que, para cada elemento da sequência, empilha
esse elemento e depois executa a quotation. É uma iteração que acontece
em tempo de compilação, por isso o código gerado é linear.
! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ] -> must build [ 10 + 20 + 30 + ]
sum-three . ! => 60
Esta é a mais estrutural das quatro: tanto o comprimento da sequência como os valores dos seus elementos moldam o código gerado. As dicas acompanham a sua construção passo a passo.
Inscreve-te no Exercism para aprenderes e dominares Factor com 47 conceitos163 exercícios, e mentoria humana real, tudo grátis.