Although the words mentioned in the previous paragraph are occasionally useful (especially the simpler
dup,
drop and
swap), you should write code that does as little stack shuffling as possible. This requires practice getting the function arguments in the right order. Nevertheless, there are certain common patterns of needed stack manipulation that are better abstracted away into their own words.
Suppose we want to define a word to determine whether a given number
n is prime. A simple algorithm is to test each number from
2 to the square root of
n and see whether it is a divisor of
n. In this case,
n is used in two places: as an upper bound for the sequence, and as the number to test for divisibility.
The word
bi applies two different quotations to the single element on the stack above them, and this is precisely what we need. For instance
5 [ 2 * ] [ 3 + ] bi yields
10
8
bi applies the quotation
[ 2 * ] to the value
5 and then the quotation
[ 3 + ] to the value
5 leaving us with
10 and then
8 on the stack. Without
bi, we would have to first
dup 5, then multiply, and then
swap the result of the multiplication with the second
5, so we could do the addition
5 dup 2 * swap 3 +
You can see that
bi replaces a common pattern of
dup, then calculate, then
swap and calculate again.
To continue our prime example, we need a way to make a range starting from
2. We can define our own word for this
[2..b], using the
[a..b] word:
: [2..b] ( n -- {2,...,n} ) 2 swap [a..b] ; inline
What's up with that
inline word? This is one of the modifiers we can use after defining a word, another one being
recursive. This will allow us to have the definition of a short word inlined wherever it is used, rather than incurring a function call.
Try our new
[2..b] word and see that it works:
6 [2..b] >array .
Using
[2..b] to produce the range of numbers from
2 to the square root of an
n that is already on the stack is easy:
sqrt floor [2..b] (technically
floor isn't necessary here, as
[a..b] works for non-integer bounds). Let's try that out
16 sqrt [2..b] >array .
Now, we need a word to test for divisibility. A quick search in the online help shows that
divisor? is the word we want. It will help to have the arguments for testing divisibility in the other direction, so we define
multiple?:
: multiple? ( a b -- ? ) swap divisor? ; inline
We can verify that both of these return
t.
9 3 divisor? .
3 9 multiple? .
Since we're going to use
bi in our
prime? definition, we need a second quotation. Our second quotation needs to test for a value in the range being a divisor of
n - in other words we need to partially apply the word
multiple?. This can be done with the word
curry, like this:
[ multiple? ] curry.
Finally, once we have the range of potential divisors and the test function on the stack, we can test whether any element satisfied divisibility with
any? and then negate that answer with
not. Our full definition of
prime looks like
: prime? ( n -- ? )
[ sqrt [2..b] ] [ [ multiple? ] curry ] bi any? not ;
Altough the definition of
prime is complicated, the stack shuffling is minimal and is only used in the small helper functions, which are simpler to reason about than
prime?.
Notice that
prime? uses two levels of quotation nesting since
bi operates on two quotations, and our second quotation contains the word
curry, which also operates on a quotation. In general, Factor words tend to be rather shallow, using one level of nesting for each higher-order function, unlike Lisps or more generally languages based on the lambda calculus, which use one level of nesting for each function, higher-order or not.
bi and its relative
tri are a small subset of the shuffle words you will use in Factor. You should also become familiar with
bi,
tri, and
bi@ by reading about them in the online help and trying them out in the listener.