Ya sabes construir una quotation a partir de piezas y ejecutarla. Una quotation es solo una secuencia de código, así que las palabras de secuencia que conoces se le aplican:
USING: kernel math prettyprint quotations sequences ;
{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call . ! => 9
[ 1 + ] dup append . ! => [ 1 + 1 + ]
append une dos quotations, concat aplana un array de ellas, map convierte datos en quotations y >quotation hace que el resultado se pueda llamar. MACRO: toma esa misma habilidad y la traslada al tiempo de compilación.
Una definición MACRO: se parece a una palabra normal, pero su cuerpo se ejecuta mientras tu programa se está compilando, no mientras se ejecuta. El cuerpo debe dejar una quotation en la pila. El compilador entonces inserta esa quotation en el punto de llamada: reemplaza la llamada a la macro por el código que produjo el cuerpo. En tiempo de ejecución ya no queda ninguna macro, solo el código generado.
USING: kernel macros sequences ;
MACRO: twice ( quot -- spliced )
dup append ;
Siempre que escribas [ 1 + ] twice, el compilador:
[ 1 + ] en la pila,dup append construye la quotation [ 1 + 1 + ],1 + 1 +, en ese lugar.Así, una palabra que llama a twice se compila como si hubieras escrito 1 + 1 + a mano:
: inc-twice ( x -- y ) [ 1 + ] twice ;
5 inc-twice . ! => 7
El contraste clave: una palabra normal que toma una quotation la llama con call en tiempo de ejecución. Una macro nunca llama a nada: le entrega al compilador código nuevo para compilar en su lugar.
Como el cuerpo se ejecuta en tiempo de compilación, las entradas de una macro deben conocerse en ese momento. Tienen que ser literales en el punto de llamada: una quotation [ … ], un número, un array, un string. Un valor que solo existe en tiempo de ejecución no se le puede entregar 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
El cuerpo de una macro es código normal que devuelve una quotation. Las palabras que ya conoces hacen la mayor parte del trabajo:
| para construir… | usa |
|---|---|
[ A B ] a partir de [ A ] y [ B ]
|
append |
| una quotation a partir de un array de ellas | concat >quotation |
| una quotation por cada elemento de una secuencia |
map, luego concat >quotation
|
Dos palabras más, de sequences y kernel, completan el conjunto.
<repetition> (en sequences) crea una secuencia de n copias de un elemento. Su efecto en la pila es ( length elt -- repetition ): la cuenta va primero. El resultado es una secuencia virtual, así que aplánala de la forma habitual:
3 [ 1 + ] <repetition> concat >quotation . ! => [ 1 + 1 + 1 + ]
curry (en kernel) antepone un valor a una quotation, produciendo una nueva quotation que empuja ese valor primero:
3 [ + ] curry . ! => [ 3 + ]
{ 1 2 3 } [ . ] curry . ! => [ { 1 2 3 } . ]
curry es como incorporas un elemento concreto a una quotation: exactamente lo que convierte { 10 20 30 } y [ + ] en [ 10 + ] [ 20 + ] [ 30 + ].
En algunos lenguajes, las macros necesitan sintaxis especial de quote/unquote y maquinaria adicional para evitar colisiones de nombres. Factor no necesita nada de eso: una quotation ya es una secuencia de palabras resueltas; el analizador buscó cada dup, + y append cuando leyó el [ … ]. Así que el cuerpo de una macro es solo la manipulación de secuencias que ya haces, que resulta devolver una quotation.
$[ … ]
Una macro devuelve una quotation para insertarla en una llamada. Cuando, en cambio, quieres un valor simple calculado en tiempo de análisis, $[ code ] ejecuta code mientras se lee el archivo e incorpora el resultado al código fuente como un literal:
! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;
CONSTANT: build-time $[ now ]
$[ … ] viene del vocabulario literals, que reúne las palabras para interpolar resultados del tiempo de análisis en literales. Se menciona para completar la información. Las cuatro plantillas que vas a escribir, twice, repeat-quot, compose-many y each-literal, son todas declaraciones MACRO:.
Mantienes el kit de herramientas de generación de código de una estación de señales. Cada macro es un molde: una pequeña plantilla que el compilador usa para estampar código especializado en cada punto de llamada.
Tú solo escribes las cuatro macros. El archivo de pruebas define unas pequeñas palabras envoltorio que llaman a tus macros (una macro solo se puede observar a través del código que genera) y luego comprueba los resultados en tiempo de ejecución de los envoltorios. Deja los envoltorios tal y como están.
En cada tarea, el cuerpo recibe en la pila las entradas literales del punto de llamada y debe dejar una quotation. Las líneas «compila a» muestran el código lineal que debería producir tu quotation.
twice toma una quotation literal y devuelve una quotation que
ejecuta la original dos veces seguidas.
! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ] -> must build [ 1 + 1 + ]
5 inc-twice . ! => 7
repeat-quot toma una quotation literal y un número literal n,
y devuelve una quotation que ejecuta la original n veces seguidas.
n = 0 devuelve la quotation vacía [ ].
! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3 -> must build [ 1 + 1 + 1 + ]
5 inc-thrice . ! => 8
compose-many toma un array literal de quotations y devuelve una
única quotation que ejecuta cada una a su vez. Un array vacío
devuelve la quotation vacía.
! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] } -> must build [ 1 + 2 * 1 - ]
5 pipeline . ! => 11
each-literal toma una secuencia literal y una quotation. Devuelve
una quotation que, para cada elemento de la secuencia, apila ese
elemento y después ejecuta la quotation: una iteración que ocurre en
tiempo de compilación, así que el código generado es lineal.
! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ] -> must build [ 10 + 20 + 30 + ]
sum-three . ! => 60
Esta es la más estructural de las cuatro: tanto la longitud de la secuencia como los valores de sus elementos dan forma al código generado. Las pistas te guían paso a paso para construirla.
Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.