Ya sabes cómo construir una quotation a partir de piezas y ejecutarla. Una quotation es simplemente una secuencia de código, así que las palabras de secuencia que ya 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 quotations, 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 de MACRO: se parece a una palabra común, 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 lugar de la llamada: reemplaza la llamada a la macro con 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 ;
Dondequiera 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
La diferencia clave: una palabra normal que recibe una quotation la ejecuta con call en tiempo de ejecución. Una macro nunca llama a nada: le entrega al compilador código nuevo para que lo compile 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 lugar de la llamada: una quotation [ … ], un número, un array, un string. Un valor que solo existe en tiempo de ejecución no se le puede pasar 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 común que devuelve una quotation. Las palabras que ya conoces hacen casi todo el 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, y 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 de pila es ( length elt -- repetition ), donde el recuento va primero. El resultado es una secuencia virtual, así que aplánala de la manera habitual:
3 [ 1 + ] <repetition> concat >quotation . ! => [ 1 + 1 + 1 + ]
curry (en kernel) antepone un valor a una quotation y produce una nueva quotation que empuja ese valor primero:
3 [ + ] curry . ! => [ 3 + ]
{ 1 2 3 } [ . ] curry . ! => [ { 1 2 3 } . ]
curry es la forma de incorporar un elemento específico en una quotation, justo lo que convierte { 10 20 30 } y [ + ] en [ 10 + ] [ 20 + ] [ 30 + ].
En algunos lenguajes, las macros necesitan una sintaxis especial de quote/unquote y maquinaria adicional para evitar conflictos de nombres. Factor no necesita nada de eso: una quotation ya es una secuencia de palabras resueltas, porque el analizador sintáctico buscó cada dup, + y append cuando leyó el [ … ]. Así que el cuerpo de una macro no es más que la manipulación de secuencias que ya haces, que resulta que devuelve 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 integra el resultado en el 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 que quede completo. Las cuatro plantillas que vas a escribir (twice, repeat-quot, compose-many y each-literal) son todas declaraciones MACRO:.
Estás manteniendo el kit de generación de código de una estación de señales. Cada macro es una plantilla: un pequeño molde que el compilador usa para estampar código especializado en cada punto de llamada.
Solo escribes las cuatro macros. El archivo de pruebas define 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 revisa los resultados en tiempo de ejecución de los envoltorios. Deja los envoltorios tal como están.
En cada tarea, el cuerpo recibe en la pila los argumentos literales del punto de llamada y debe dejar un quotation. Las líneas «compila a» muestran el código lineal que debería producir tu quotation.
twice toma un quotation literal y devuelve un quotation que ejecuta el
original dos veces seguidas.
! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ] -> must build [ 1 + 1 + ]
5 inc-twice . ! => 7
repeat-quot toma un quotation literal y un conteo literal n,
y devuelve un quotation que ejecuta el original n veces seguidas.
n = 0 devuelve el quotation vacío [ ].
! : 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 un único
quotation que ejecuta cada una en orden. Un array vacío devuelve el quotation
vacío.
! : 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 un quotation. Devuelve un
quotation que, para cada elemento de la secuencia, empuja ese elemento y
luego ejecuta el 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 llevan paso a paso por su construcción.
Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.