Until now we have cheated a bit, and tried to avoid writing examples that would have been too complex to write in concatenative style. Truth is, you
will find occasions where this is too restrictive. Parsing words can ease some of these restrictions, and Factor comes with a few to handle the most common annoyances.
One thing you may want to do is to actually name local variables. The
:: word works like
:, but allows you to actually bind the name of stack parameters to variables, so that you can use them multiple times, in the order you want. For instance, let us define a word to solve quadratic equations. I will spare you the purely stack-based version, and present you a version with locals (this will require the
locals vocabulary):
:: solveq ( a b c -- x )
b neg
b b * 4 a c * * - sqrt
+
2 a * / ;
In this case we have chosen the + sign, but we can do better and output both solutions:
:: solveq ( a b c -- x1 x2 )
b neg
b b * 4 a c * * - sqrt
[ + ] [ - ] 2bi
[ 2 a * / ] bi@ ;
You can check that this definition works with something like
2 -16 30 solveq, which should output both
3.0 and
5.0. Apart from being written in RPN style, our first version of
solveq looks exactly the same it would in a language with local variables. For the second definition, we apply both the
+ and
- operations to -b and delta, using the combinator
2bi, and then divide both results by 2a using
bi@.
There is also support for locals in quotations - using
[| - and methods - using
M:: - and one can also create a scope where to bind local variables outside definitions using
[let. Of course, all of these are actually compiled to concatenative code with some stack shuffling. I encourage you to browse examples for these words, but bear in mind that their usage in practice is actually much less prominent than one would expect - about 1% of Factor's own codebase.
Another common case happens when you need to add values to a quotation in specific places. You can partially apply a quotation using
curry. This assumes that the value you are applying should appear leftmost in the quotation; in the other cases you need some stack shuffling. The word
with is a sort of partial application with a hole. It also curries a quotation, but uses the third element on the stack instead of the second. Also, the resulting curried quotation will be applied to an element inserting it in the second position.
The example from the documentation probably tells more than the above sentence -- try writing:
1 { 1 2 3 } [ / ] with map
Let me take again
prime?, but this time write it without using helper words:
: prime? ( n -- ? )
[ sqrt 2 swap [a,b] ] [ [ swap divisor? ] curry ] bi any? not ;
Using
with instead of
curry, this simplifies to
: prime? ( n -- ? )
2 over sqrt [a,b] [ divisor? ] with any? not ;
If you are not able to visualize what is happening, you may want to consider the
fry vocabulary. It defines
fried quotations; these are quotations that have holes in them - marked by
_ - that are filled with values from the stack.
The first quotation is rewritten more simply as
[ '[ 2 _ sqrt [a,b] ] call ]
Here we use a fried quotation - starting with
'[ - to inject the element on the top of the stack in the second position, and then use
call to evaluate the resulting quotation. The second quotation can be rewritten as follows:
[ '[ _ swap divisor? ] ]
so an alternative definition of
prime? is
: prime? ( n -- ? )
[ '[ 2 _ sqrt [a,b] ] call ] [ '[ _ swap divisor? ] ] bi any? not ;
Depending on your taste, you may find this version more readable. In this case, the added clarity is probably lost due to the fact that the fried quotations are themselves inside quotations, but occasionally their use can do a lot to simplify the flow.
Finally, there are times where one just wants to give names to variables that are available inside some scope, and use them where necessary. These variables can hold values that are global, or at least not local to a single word. A typical example could be the input and output streams, or database connections.
For this purpose, Factor allows you to create
dynamic variables and bind them in scopes. The first thing is to create a
symbol for a variable, say
SYMBOL: favorite-language
Then one can use the word
set to bind the variable and
get to retrieve its values, like
"Factor" favorite-language set
favorite-language get
Scopes are nested, and new scopes can be created with the word
with-scope. Try for instance
: on-the-jvm ( -- )
[
"Scala" favorite-language set
favorite-language get .
] with-scope ;
If you run
on-the-jvm,
"Scala" will be printed, but after execution,
favorite-language get will hold
"Factor" as its value.
All the tools that we have seen in this section should only be used when absolutely necessary, as they break concatenativity and make words less easy to factor. However, they can greatly increase clarity when needed. Factor has a very practical approach and does not shy from offering features that are less pure but nevertheless often useful.