Stack juggling is fine for short words. When a word has several
inputs and you need to refer to them by name, Factor offers
locals — named bindings introduced with :: (for word
definitions) or [| | ] (for inline lambdas).
:: ( inputs -- outputs )Replace : with :: and the input names become local variables in
the body:
USING: locals ;
:: hypotenuse ( a b -- c )
a a *
b b *
+ sqrt ;
Compare with the stack-shuffling version:
! DOCTEST: SKIP (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
[ sq ] bi@ + sqrt ;
Both are valid — locals shine when an input is referenced more than once or when the order of operations doesn't naturally line up with stack order.
:>
Inside a :: body (or an inline [| ... |] lambda), :> pops a
value off the data stack and binds it to a fresh local:
value :> name ! immutable binding
value :> name! ! mutable binding (name! is its setter)
The immutable form is the common one — use it to name an intermediate so the body reads top-to-bottom:
USING: locals math ;
:: average-3 ( x y z -- avg )
x y + z + :> sum
sum 3 / ;
The ! flavour declares the local as mutable; later in the
body, value name! (no colon, no :>) reassigns it:
USING: kernel locals math math.order ;
:: clamped ( x lo hi -- y )
x :> v!
v lo < [ lo v! ] when
v hi > [ hi v! ] when
v ;
Mutable locals shine inside the iteration words you'll meet in
later exercises (e.g. while).
[let — a scope from anywhere:> only works inside a lexical scope, which :: and
[| ... |] create automatically. To introduce locals from a
plain : word or at the listener, wrap the code in [let ... ]:
[let code :> name code :> name ... body ]
Each :> name binds the previous expression's top-of-stack value;
the bindings are visible to the rest of the form.
USING: locals math ;
[let 3 :> x 4 :> y x x * y y * + ] . ! => 25
[let is the bridge between stack-style : definitions and
named bindings — handy when only a slice of a word benefits from
locals.
[| inputs | body ]
Inside a quotation, [| inputs | body ] introduces locals that
the body can refer to by name. This is especially useful with
higher-order words like assoc-map, whose quotation receives key
and value:
USING: assocs locals ;
H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }
The same shape works for map-index (in [sequences][sequences]),
whose quotation receives the element and its index. The names
make clear which operand plays which role:
USING: locals sequences ;
! Divide each element by its 1-based position:
{ 10 80 270 } [| elt i | elt i 1 + / ] map-index .
! => { 10 40 90 }
The lambda's stack effect is determined by the input list and what its body produces.
Reach for locals when:
dup/swap/rot to fix it is hard to read.assoc-map).When the body is a clean linear flow, plain : definitions stay
shorter and at least as readable.
assoc-map — one entry at a timeassoc-map (in assocs) maps a quotation over each
entry of an associative collection:
assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )
A locals lambda is the cleanest way to write the quotation.
cond revisitedWhen you need to dispatch on f or on a numeric value, cond is
exactly the right shape — no case needed because there's no single
value to compare against.
You're back in the kitchen, refining the lasagna recipe from Leah's Luscious Lasagna. The tasks here ask you to juggle several inputs at once, which is exactly when locals — Factor's named bindings — earn their keep.
Define cooking-status to take a timer reading off the stack and
return a status string.
0 → "Lasagna is done."
f (the timer was never set) → "You forgot to set the timer."
"Not done, please wait."
12 cooking-status . ! => "Not done, please wait."
0 cooking-status . ! => "Lasagna is done."
f cooking-status . ! => "You forgot to set the timer."
Define preparation-time to take an array of layer names and an
average number of minutes per layer, and return the total preparation
time.
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18
Define quantities to take an array of layer names and return two
values: the grams of noodles needed (50 g per noodle layer) and the
litres of sauce needed (1/5 litre per sauce layer).
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5
Your friend sends a list of ingredients; the last item is their
secret. Define add-secret-ingredient to take their list and yours
and return your list with the secret appended.
{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }
The cookbook recipe yields two portions. Define scale-recipe to
take a recipe (a hashtable from ingredient name to amount) and a
target number of portions, and return a new recipe scaled
appropriately.
H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.