Tracks
/
Factor
Factor
/
Exercises
/
Signal Stencils
Signal Stencils

Signal Stencils

Learning Exercise

Introduction

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.

What a macro is

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. runs the body with [ 1 + ] on the stack,
  2. dup append builds the quotation [ 1 + 1 + ],
  3. splices its code, 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.

Inputs must be literals

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

The toolkit

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

Why this is simpler than it sounds

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 close cousin: $[ … ]

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.

Instructions

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.

1. Twice over

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

2. N repetitions

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

3. Compose many

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

4. Unrolled iteration

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.

Edit via GitHub The link opens in a new window or tab
Factor Exercism

Ready to start Signal Stencils?

Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.