Factor is a concatenative, stack-oriented and object-functional programming language first developed by Slava Pestov in 2003 as a scripting language for a game engine. Factor unites the simplicity and durability of Forth's stack-procedural paradigm and extensible word-based syntax, with the applicative, point-free and functional styles of Joy and Lisp.
In Factor, whitespace is the apply
operator, the default operation. All words are first class objects; in fact, almost everything is a first-class object which means tons of possibility for extensible syntax, new types and literals, and all the other awesomeness introduced by Homoiconicity.
Unlike Forth, Factor prefers to use the many, many different higher-order words at its disposal over shuffle words, which should be avoided for the sake of readability and idiomaticity.
You can download a Factor binary, or find Factor in active development on GitHub.
Oh, its documentation is amazing, and the Listener UI / IDE (written in pure Factor) has the documentation offline and searchable. :D
I mentioned above that Factor was a scripting language -- that was back when it ran on the JVM, too. Factor, its runtime and its optimising native-code compiler are 97% pure Factor, with just 3% being low-level C++ for the VM and bootstrap.
I guess we'll go top-to-bottom, Iunno.
Link to this answer, Link to Edit.
Length 1
For length 1, let's start at the most basic building block of Factor: the word that lets us make more words.
Factor's pretty verbose so there's not much to do with one byte, because the "words" are literally words, and there's a lot of whitespace everywhere.
This means the fun in golfing comes not from shortening identifiers, but from re-Factoring, which is where Factor gets its name. :D
Factor gets a lot of its pleasant syntax from Forth. The simplest flavour of word-definition word, :
is an example.
Use it like:
: word-name ( input -- output ) word-body ;
The word will then be callable by word-name
, and word-body
will execute, taking and putting things to and from the stack. word-name
's stack effect must match its actual effect on the stack. If add
takes two numbers from the stack and adds them together, leaving the result on the stack,
: add ( a b -- x ) + ;
Then its stack effect is that of the only word in its body (part of the power of concatenative languages). The identifiers in the effect don't matter, just that they are a proper representation.
Length 2
Another word-definition word, ::
, allows the use of the :>
lexical-variable-binding word, and causes inputs to be bound to the names in the stack effect.
:: join-strings ( a b -- x ) a b suffix! ;
To bind a value to a name:
:: var-demo ( a b c -- ) a c + b * sqrt :> val ;
val
goes out of scope when the word is done executing.
Length 3
[ ]
Here, I'll let you have a guess at what this is. Hint: It's not an array.
You don't know?
It's a quotation -- it allows putting an executable block of literal code on the stack, that the higher-order functional and applicative words can manipulate.
Brackets for blocks come from Smalltalk syntax (with the locals
vocabulary you also get [| param | code ]
which kind of resembles Smalltalk's [ :param | code ]
).
Length 4
I'll try to do something more interesting this time, as fede s. suggested in the comments:
2bi@
Before I explain what this is, I should probably first explain something about Factor identifiers.
A valid Factor identifier is any string which matches the following:
^[^"][^\s]+$
Yes, that's it. Any identifier can contain any character that isn't whitespace, and it can't begin with one or more "
s, because strings are special to the lexer. It can have "
elsewhere within it, though.
The NUL byte, the character at UTF8 888 and the byte at 127, ASCII DEL are all valid identifiers. For portability, sometimes SYMBOL
s may be stripped of their non-ASCII bytes (Windows, for instance).
This may be rather shocking to the C people and the Python people (and even the Lisp people, who allow some nice stuff in names), but fear not, for you will come to like it.
So 2bi@
is just one identifier, one name for one function. But what does it do? Well, the bi@
function applies one quotation to two items on the stack:
"5" "10" [ string>number ] bi@
--- Data stack:
5
10
(bi stands for bifurcate, if you were curious.)
"5" "10" "15" "20" [ append string>number ] 2bi@
--- Data stack:
510
1520
2bi@
, like 2map
, takes a quotation with stack effect ( obj1 obj2 -- ... )
, which gets applied first to w
and x
, then y
and z
in pairs.
Length 5
Finally, a word for working with sequences. Factor is no APL / J / K / Octave / what have you, but its array processing skills are... close... kinda.
union
If you're familar with Python (like I am), then you may recognise this better as the |
operator on set
s:
{ 1 2 3 } { 2 3 4 } union
--- Data stack:
{ 1 2 3 4 }
Removes duplicates while performing element-wise OR. Technically, the union
word is from the sets
vocabulary and not the sequences
vocab. This is because, like Python, sets are special, super-fast immutable sequences that don't allow duplicate entries.
Also much like Python, any sequence (byte vector, hashtable, set, etc) that implements the Sequence protocol (__iter__
being the equivalent in Python) is considered fair game and supports a well-defined set (hah) of operations, so any word that works on a generic sequence A will also work on a generic set B if both have the right method dispatch.
Length 6
Ooh, more arrays! (Note that arrays are immutable; all operations on them yield new arrays and the old ones are GC'd. Vectors are mutable and slower, but we'll cross that bridge when we get there.)
2array
This is a highly useful word. Any guess at what it does? Here:
IN: scratchpad 1 2 2array
--- Data stack:
{ 1 2 }
IN: scratchpad { 3 4 } 2array
--- Data stack:
{ { 1 2 } { 3 4 } }
Well shucks, it takes two things off the stack and makes a new array from them. 1array
, 3array
and 4array
all do exactly what you'd expect, and narray
takes an arbitrary n
argument, to avoid messy concatenation code.
If I may digress yet again about naming...
In Java or Python, one might call this function values2array
or valuesToArray
or even values2Array
. However, here in Factor-land, we have some conventions.
Remember my spiel about identifiers? Numeric digits in names should only be used for indicating real numbers or counts of things; in particular, this means 2array
should not be read as to-array
but as two-array
.
The word which does to-array
is called >array
. The word which turns a number to a string is number>string
, and the word which turns an object's slots to an array is tuple>array
. See a pattern? Hmm.
Users of Racket will feel right at home with these sorts of names, since Racket uses stuff like number->string
.