When the stack is not enough
Factor handbook ยป Guided tour of Factor

Prev:Metaprogramming
Next:Input/Output


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 defition 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.