Trilhas
/
Factor
Factor
/
Exercícios
/
Estênceis de Sinal
Estênceis de Sinal

Estênceis de Sinal

Exercício de aprendizagem

Introdução

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.

O que é uma macro

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. roda o corpo com [ 1 + ] na pilha,
  2. dup append constrói a quotation [ 1 + 1 + ],
  3. insere o código dela, 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.

As entradas precisam ser literais

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 kit de ferramentas

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

Por que isso é mais simples do que parece

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.

Um primo próximo: $[ … ]

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

Instruções

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.

1. Duas vezes

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

2. N repetições

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

3. Compor várias

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

4. Iteração desenrolada

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.

Editar via GitHub O link abre em uma nova janela ou aba
Factor Exercism

Tudo pronto para começar Estênceis de Sinal?

Crie sua conta no Exercism para aprender e dominar Factor com 47 conceitos163 exercícios e mentoria humana de verdade, tudo de graça.