Some loops can't be written as "do this for each element of a
sequence" — you have to keep going until some condition flips.
Factor's while combinator is the tool for that: a predicate
quotation says "keep going?", a body quotation does one step's
work, and while repeats until the predicate gives f.
while and until
while ( pred body -- )
until ( pred body -- )
pred and body are both quotations that operate on the data
stack. while keeps looping while pred leaves a truthy value;
until is the opposite: it keeps looping until pred becomes true
(that is, while pred leaves f).
A single piece of state can flow on the stack between the predicate and the body:
USING: kernel math ;
5 [ dup 0 > ] [ dup . 1 - ] while drop
! prints 5 4 3 2 1
Trace: the predicate dup 0 > peeks at the running counter; the
body dup . 1 - prints it and decrements it; the loop stops once
the counter reaches 0. The trailing drop discards that final
0 from the stack.
while* — keep the predicate's valuewhile* ( pred body -- )
while only cares whether its predicate left a true value — it
throws the value itself away before running the body. while*
instead hands that value to the body. Reach for it whenever the
test and the body want the same freshly-computed thing: a lookup,
a match, a parsed token.
Here each name points to the next one in a chain. The predicate
looks the current name up; as long as there is a next name, while*
passes it to the body, which prints it and carries on from there:
USING: assocs kernel locals prettyprint ;
:: print-chain ( start links -- )
start [ links at ] [ dup . ] while* ;
"a" H{ { "a" "b" } { "b" "c" } } print-chain
! prints "b" then "c"
links at returns the next name, or f once a name has no entry.
While it returns a name, while* gives that name to dup ., which
prints it and leaves it as the key for the next lookup. The first
f stops the loop — while* discards it, so nothing is left over.
When the loop carries more than one value — a remaining list and
a running total, say — stack juggling gets noisy. The cleanest
pattern is to declare locals with :::
:: word ( inputs -- outputs ) ... ;
Inside the body, :> name! adds a mutable local; reads use
name, writes use name! (which consumes the new value off the
stack and assigns it).
USING: kernel locals math sequences ;
! Walk a sequence from the front, summing as we go
:: sum-by-hand ( seq -- total )
seq :> rest!
0 :> running!
[ rest empty? ] [
rest unclip [ rest! ] dip
running + running!
] until
running ;
Two locals here (rest and running) cover the whole loop's
state: the predicate inspects them, the body updates them, and
the final value is returned at the end.
unclip ( seq -- rest first ) peels off the first element of a
sequence — handy when the loop wants to consume a list one
element per iteration. The [ rest! ] dip form updates rest
to the remaining tail while leaving the freshly-popped element
on the stack for the body to use.
Your friend Li Mei runs a juice bar where she sells delicious mixed fruit juices. You are a frequent customer in her shop and realised you could make her life a little easier with some Factor code.
Li Mei likes to tell her customers in advance how long they have to wait for a juice from the menu. She has a hard time remembering the exact numbers because the time it takes to mix the juices varies:
"Pure Strawberry Joy" takes 0.5 minutes."Energizer" and "Green Garden" take 1.5 minutes each."Tropical Island" takes 3 minutes."All or Nothing" takes 5 minutes.2.5 minutes.Define time-to-mix-juice to take a juice name off the stack and
return the number of minutes it takes to mix that drink.
"Tropical Island" time-to-mix-juice .
! => 3
"Berries & Lime" time-to-mix-juice .
! => 2.5
A lot of Li Mei's creations include lime wedges, either as an ingredient or as part of the decoration. So when she starts her shift in the morning she needs to make sure the bin of lime wedges is full for the day ahead.
Define wedges-from-lime taking a lime size and returning the
number of wedges it produces — 6 from a "small" lime, 8
from a "medium", 10 from a "large".
Then define limes-to-cut taking the number of needed wedges
and an array of limes (lime sizes in cut order). It cuts the
limes one by one, in order, and stops as soon as either it has
enough wedges or it runs out of limes. Return the number of
limes it had to cut.
25 { "small" "small" "large" "medium" "small" } limes-to-cut .
! => 4
Li Mei likes to keep track of how long it will take to mix the orders customers are waiting for.
Define order-times to take an array of orders and return an
array with the time to mix each order, in the same order.
{ "Energizer" "Tropical Island" } order-times .
! => { 1.5 3 }
Li Mei always works until 3 pm. Then her employee Dmitry takes over. There are often drinks that have been ordered but not prepared yet when Li Mei's shift ends; Dmitry will then prepare the remaining juices.
To make the hand-over easier, define remaining-orders taking
the number of time-left minutes in Li Mei's shift and an array
of orders (juices that have been ordered but not prepared yet).
Return the orders Dmitry will need to take over — the ones Li
Mei does not have time to start before her shift ends.
time-left is always greater than 0. The orders are prepared
in the order in which they appear in the array. Once Li Mei
starts a juice she always finishes it, even if she has to work a
little longer. If she has time to start every order, return an
empty array.
5 { "Energizer" "All or Nothing" "Green Garden" } remaining-orders .
! => { "Green Garden" }
Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.