We will now define our first function. Factor has slightly odd naming of functions: since functions are read from left to right, they are simply called
words, and this is what we'll call them from now on. Modules in Factor define words in terms of previous words and these sets of words are then called
vocabularies.
Suppose we want to compute the factorial. To start with a concrete example, we'll compute the factorial of
10, so we start by writing
10 on the stack. Now, the factorial is the product of the numbers from
1 to
10, so we should produce such a list of numbers first.
The word to produce a range is called
[a..b] (tokenization is trivial in Factor because words are always separated by spaces, so this allows you to use any combination of non-whitespace characters as the name of a word; there are no semantics to the
[, the
.. and the
] in
[a..b] since it is just a token like
foo or
bar).
The range we want starts with
1, so we can use the simpler word
[1..b] that assumes the range starts at
1 and only expects the value at the top of the range to be on the stack. If you write
[1..b] in the listener, Factor will prompt you with a choice, because the word
[1..b] is not imported by default. Factor is able to suggest you import the
ranges vocabulary, so choose that option and proceed.
You should now have on your stack a rather opaque structure which looks like
T{ range f 1 10 1 }
This is because our range functions are lazy and only create the range when we attempt to use it. To confirm that we actually created the list of numbers from
1 to
10, we convert the lazy response on the stack into an array using the word
>array. Enter that word and your stack should now look like
{ 1 2 3 4 5 6 7 8 9 10 }
which is promising!
Next, we want to take the product of those numbers. In many functional languages, this could be done with a function called reduce or fold. Let's look for one. Pressing
F1 in the listener will open a contextual help system, where you can search for
reduce. It turns out that
reduce is actually the word we are looking for, but at this point it may not be obvious how to use it.
Try writing
1 [ * ] reduce and look at the output: it is indeed the factorial of
10. Now,
reduce usually takes three arguments: a sequence (and we had one on the stack), a starting value i (this is the
1 we put on the stack next) and a binary operation. This must certainly be the
*, but what about those square brackets around the
* ?
If we had written just
*, Factor would have tried to apply multiplication to the topmost two elements on the stack, which is not what we wanted. What we need is a way to get a word onto the stack without applying it. Keeping to our textual metaphor, this mechanism is called a
quotation. To quote one or more words, you just surround them with
[ and
] (leaving spaces!). What you get is an anonymous function, which can be shuffled around, manipulated and called.
Let's type the word
drop into the listener to empty the stack, and try writing what we have done so far in a single line:
10 [1..b] 1 [ * ] reduce. This will leave
3628800 on the stack as expected.
We now want to define a word for factorial that can be used whenever we want a factorial. We will call our word
fact(although
! is customarily used as the symbol for factorial, in Factor
! is the word used for comments). To define it, we first need to use the word
:. Then we put the name of the word being defined, then the
stack effects and finally the body, ending with the
; word:
: fact ( n -- n! ) [1..b] 1 [ * ] reduce ;
What is a stack effect? In our case it is
( n -- n! ). Stack effects are how you document the inputs from the stack and outputs to the stack for your word. You can use any identifier to name the stack elements, here we use
n. Factor will perform a consistency check that the number of inputs and outputs you specify agrees with what the body does.
If you try to write
: fact ( m n -- ..b ) [1..b] 1 [ * ] reduce ;
Factor will signal an error that the 2 inputs
m and
n are not consistent with the body of the word. To restore the previous correct definition press
Ctrl+P two times to get back to the previous input and then enter it.
The stack effects in definitions act both as a documentation tool and as a very simple type system, which helps to catch a few errors.
In any case, you have succesfully defined your first word: if you write
10 fact in the listener you can prove it.
Notice that the
1 [ * ] reduce part of the definition sort of makes sense on its own, being the product of a sequence. The nice thing about a concatenative language is that we can just factor this part out and write
: prod ( {x1,...,xn} -- x1*...*xn ) 1 [ * ] reduce ;
: fact ( n -- n! ) [1..b] prod ;
Our definitions have become simpler and there was no need to pass parameters, rename local variables, or do anything else that would have been necessary to refactor our function in most languages.
Of course, Factor already has a word for calculating factorial (there is a whole
math.
factorials vocabulary, including many variants of the usual factorial) and a word for calculating product (
product in the
sequences vocabulary), but as it often happens, introductory examples overlap with the standard library.