You already know how to build a quotation out of pieces and run it. A quotation is just a sequence of code, so the sequence words you know apply to it:
USING: kernel math prettyprint quotations sequences ;
{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call . ! => 9
[ 1 + ] dup append . ! => [ 1 + 1 + ]
append joins two quotations, concat flattens an array of them,
map turns data into quotations, and >quotation makes the
result callable. MACRO: takes that exact skill and moves it to
compile time.
A MACRO: definition looks like an ordinary word, but its body
runs while your program is being compiled, not while it runs.
The body must leave one quotation on the stack. The compiler
then splices that quotation into the call site — it replaces
the macro call with the code the body produced. At runtime there
is no macro left, only the generated code.
USING: kernel macros sequences ;
MACRO: twice ( quot -- spliced )
dup append ;
Wherever you write [ 1 + ] twice, the compiler:
[ 1 + ] on the stack,dup append builds the quotation [ 1 + 1 + ],1 + 1 +, into that spot.So a word that calls twice compiles as if you had written
1 + 1 + by hand:
: inc-twice ( x -- y ) [ 1 + ] twice ;
5 inc-twice . ! => 7
The key contrast: a normal word that takes a quotation calls it
at runtime. A macro never calls anything — it hands the compiler
new code to compile in its place.
Because the body runs at compile time, a macro's inputs must be
known then. They have to be literals at the call site — a
[ … ] quotation, a number, an array, a string. A value that only
exists at runtime cannot be handed to a 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
A macro body is ordinary code that returns a quotation. Words you have already met do most of the work:
| to build… | use |
|---|---|
[ A B ] from [ A ] and [ B ]
|
append |
| one quotation from an array of them | concat >quotation |
| a quotation per element of a sequence |
map, then concat >quotation
|
Two more words, from sequences and kernel, complete the kit.
<repetition> (in sequences) makes a sequence of n copies
of one element. Its stack effect is ( length elt -- repetition )
— the count comes first. The result is a virtual sequence, so
flatten it the usual way:
3 [ 1 + ] <repetition> concat >quotation . ! => [ 1 + 1 + 1 + ]
curry (in kernel) prepends a value to a quotation,
producing a new quotation that pushes that value first:
3 [ + ] curry . ! => [ 3 + ]
{ 1 2 3 } [ . ] curry . ! => [ { 1 2 3 } . ]
curry is how you bake one specific element into a quotation —
exactly what turns { 10 20 30 } and [ + ] into
[ 10 + ] [ 20 + ] [ 30 + ].
In some languages, macros need special quote/unquote syntax and
extra machinery to avoid name clashes. Factor needs none of that:
a quotation is already a sequence of resolved words — the parser
looked up each dup, +, and append when it read the [ … ].
So a macro body is just the sequence manipulation you already do,
that happens to return a quotation.
$[ … ]
A macro returns a quotation to be spliced into a call. When you
instead want a plain value computed at parse time, $[ code ]
runs code while the file is being read and folds the result
into the source as a literal:
! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;
CONSTANT: build-time $[ now ]
$[ … ] comes from the literals vocabulary, which
collects the words for interpolating parse-time results into
literals. It is mentioned for completeness. The four stencils you'll write —
twice, repeat-quot, compose-many, and each-literal — are
all MACRO: declarations.
You're maintaining a signal station's code-generation toolkit. Each macro is a stencil — a small template the compiler uses to stamp out specialised code at every call site.
You only write the four macros. The test file defines small wrapper words that call your macros (a macro can only be observed through the code it generates), then checks the wrappers' runtime results. Leave the wrappers as they are.
For each task, the body receives the call site's literal inputs on the stack and must leave one quotation. The "compiles to" lines show the straight-line code your quotation should produce.
twice takes a literal quotation and returns a quotation that
runs the original twice in a row.
! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ] -> must build [ 1 + 1 + ]
5 inc-twice . ! => 7
repeat-quot takes a literal quotation and a literal count n,
and returns a quotation that runs the original n times in a row.
n = 0 returns the empty quotation [ ].
! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3 -> must build [ 1 + 1 + 1 + ]
5 inc-thrice . ! => 8
compose-many takes a literal array of quotations and returns a
single quotation that runs each one in turn. An empty array
returns the empty quotation.
! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] } -> must build [ 1 + 2 * 1 - ]
5 pipeline . ! => 11
each-literal takes a literal sequence and a quotation. It
returns a quotation that, for each element of the sequence, pushes
that element and then runs the quotation — iteration that happens
at compile time, so the generated code is straight-line.
! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ] -> must build [ 10 + 20 + 30 + ]
sum-three . ! => 60
This is the most structural of the four: both the sequence's length and its element values shape the generated code. The hints walk through building it one step at a time.
Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.