Você já sabe como construir uma quotation a partir de pedaços e rodá-la. Uma quotation é só uma sequência de código, então as palavras de sequência que você conhece também se aplicam a ela:
USING: kernel math prettyprint quotations sequences ;
{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call . ! => 9
[ 1 + ] dup append . ! => [ 1 + 1 + ]
append junta duas quotations, concat achata um array delas, map transforma dados em quotations, e >quotation faz com que o resultado possa ser chamado. MACRO: pega exatamente essa habilidade e a leva para o tempo de compilação.
Uma definição MACRO: parece uma palavra comum, mas o corpo dela roda enquanto seu programa está sendo compilado, e não enquanto ele roda. O corpo precisa deixar uma quotation na pilha. O compilador então insere essa quotation no local da chamada: ele substitui a chamada da macro pelo código que o corpo produziu. Em tempo de execução não sobra nenhuma macro, só o código gerado.
USING: kernel macros sequences ;
MACRO: twice ( quot -- spliced )
dup append ;
Onde quer que você escreva [ 1 + ] twice, o compilador:
[ 1 + ] na pilha,dup append constrói a quotation [ 1 + 1 + ],1 + 1 +, nesse ponto.Então uma palavra que chama twice compila como se você tivesse escrito 1 + 1 + à mão:
: inc-twice ( x -- y ) [ 1 + ] twice ;
5 inc-twice . ! => 7
O contraste principal: uma palavra normal que recebe uma quotation aplica call a ela em tempo de execução. Uma macro nunca chama nada: ela entrega ao compilador código novo para compilar no lugar dela.
Como o corpo roda em tempo de compilação, as entradas de uma macro precisam ser conhecidas naquele momento. Elas têm que 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 comum que retorna uma quotation. As palavras que você já conheceu fazem a maior parte do trabalho:
| para construir… | use |
|---|---|
[ A B ] a partir de [ A ] e [ B ]
|
append |
| uma quotation a partir de um array delas | concat >quotation |
| uma quotation por elemento de uma sequência |
map, depois concat >quotation
|
Mais duas palavras, de sequences e kernel, completam o kit.
<repetition> (em sequences) cria uma sequência de n cópias de um elemento. O efeito na pilha dela é ( length elt -- repetition ): a contagem vem primeiro. O resultado é uma sequência virtual, então achate-a do jeito de sempre:
3 [ 1 + ] <repetition> concat >quotation . ! => [ 1 + 1 + 1 + ]
curry (em kernel) coloca um valor no início de uma quotation, produzindo uma nova quotation que empilha esse valor primeiro:
3 [ + ] curry . ! => [ 3 + ]
{ 1 2 3 } [ . ] curry . ! => [ { 1 2 3 } . ]
curry é como você embute um elemento específico em uma quotation, exatamente o que transforma { 10 20 30 } e [ + ] em [ 10 + ] [ 20 + ] [ 30 + ].
Em algumas linguagens, macros precisam de uma sintaxe especial de quote/unquote e de maquinário extra para evitar conflitos de nomes. O Factor não precisa de nada disso: uma quotation já é uma sequência de palavras resolvidas, pois o parser consultou cada dup, + e append quando leu o [ … ]. Então o corpo de uma macro é só a manipulação de sequências que você já faz, que por acaso retorna uma quotation.
$[ … ]
Uma macro retorna uma quotation para ser inserida em uma chamada. Quando você quer, em vez disso, um valor comum calculado em tempo de parsing, $[ code ] roda code enquanto o arquivo está sendo lido e incorpora o resultado ao código-fonte como um literal:
! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;
CONSTANT: build-time $[ now ]
$[ … ] vem do vocabulário literals, que reúne as palavras para interpolar resultados de tempo de parsing em literais. Ele é mencionado apenas por completeza. Os quatro estênceis que você vai escrever, twice, repeat-quot, compose-many e each-literal, são todos declarações MACRO:.
Você cuida do kit de geração de código de uma estação de sinais. Cada macro é um estêncil: um pequeno molde que o compilador usa para carimbar código especializado em cada ponto de chamada.
Você escreve apenas as quatro macros. O arquivo de testes define pequenas palavras wrapper que chamam suas macros (uma macro só pode ser observada por meio do código que ela gera) e depois verifica os resultados em tempo de execução dos wrappers. Deixe os wrappers como estão.
Para cada tarefa, o corpo recebe as entradas literais do ponto de chamada na pilha e deve deixar uma quotation. As linhas "compila para" mostram o código linear que sua quotation deve produzir.
twice recebe uma quotation literal e retorna 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 retorna uma quotation que executa a original n vezes
seguidas. n = 0 retorna 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 retorna uma
única quotation que executa cada uma delas em sequência. Um array
vazio retorna 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. Ela
retorna uma quotation que, para cada elemento da sequência, empilha
esse elemento e depois executa a quotation: iteração que acontece em
tempo de compilação, então 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 quanto os valores de seus elementos moldam o código gerado. As dicas mostram como construí-la passo a passo.
Crie sua conta no Exercism para aprender e dominar Factor com 47 conceitos163 exercícios e mentoria humana de verdade, tudo de graça.