What general tips do you have for golfing in Factor? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to Factor (e.g. "remove comments" is not an answer).
18 Answers
math.unicode
this thing is a golfing jewel. http://docs.factorcode.org/content/vocab-math.unicode.html
Here, I should let it speak for itself.
Word Stack effect Equivalent (Byte Char) Savings (best case)
¬ ( obj -- ? ) not 1 2
² ( m -- n ) 2 ^ , 2^ 1 2
³ ( m -- n ) 3 ^ 1 2
¹ ( m -- n ) 1 ^ 1 2
¼ ( -- value ) 1/4 , 1 4 / 1 2
½ ( -- value ) 1/2 1 2
¾ ( -- value ) 3/4 1 2
× ( x y -- z ) * -1 0
÷ ( x y -- z ) / -1 0
Π ( seq -- n ) product 5 6
Σ ( seq -- n ) sum 1 2
π ( -- pi ) pi 0 0
φ ( -- n ) phi 1 2
‰ ( m -- n ) 1000 / 4 5
‱ ( m -- n ) 10000 / 5 6
ⁿ ( x y -- z ) ^ -1 0
⅓ ( -- value ) 1/3 1 2
⅔ ( -- value ) 2/3 1 2
⅕ ( -- value ) 1/5 1 2
⅖ ( -- value ) 2/5 1 2
⅗ ( -- value ) 3/5 1 2
⅘ ( -- value ) 4/5 1 2
⅙ ( -- value ) 1/6 1 2
⅚ ( -- value ) 5/6 1 2
⅛ ( -- value ) 1/8 1 2
⅜ ( -- value ) 3/8 1 2
⅝ ( -- value ) 5/8 1 2
⅞ ( -- value ) 7/8 1 2
Those are the simple constants. Now for the functions:
Word Stack effect Equivalent (Byte Char) Savings (best case)
∀ ( seq quot -- ? ) all? 1 3
∃ ( seq quot -- ? ) any? 1 3
∄ ( seq quot -- ? ) none? 2 4
∈ ( elt seq -- ? ) member? 4 6
∉ ( elt seq -- y ) ∈ ¬ 3 2
∋ ( seq elt -- ? ) swap member? 9 11
∌ ( seq elt -- y ) ∋ ¬ 3 2
− ( x y -- z ) - -1 0
∕ ( x y -- z ) / -1 0
∖ ( s1 s2 -- set ) diff 1 3
√ ( x -- y ) sqrt 1 3
∛ ( x -- y ) ⅓ ^ 2 2
∜ ( x -- y ) ¼ ^ 2 2
∞ ( -- value ) 1/0. 1 3
∧ ( o1 o2 -- ? ) and 0 2
∨ ( o1 o2 -- ? ) or -1 1
∩ ( s1 s2 -- set ) intersect 6 8
∪ ( s1 s2 -- set ) union 2 4
≠ ( o1 o2 -- ? ) = ¬ 1 2
≤ ( x y -- ? ) <= -1 1
≥ ( x y -- ? ) >= -1 1
⊂ ( s1 s2 -- ? ) subset? 4 6
⊃ ( s1 s2 -- ? ) superset? 6 8
⊄ ( s1 s2 -- ? ) ⊂ ¬ 3 2
⊅ ( s1 s2 -- ? ) ⊃ ¬ 3 2
⊼ ( o1 o2 -- ? ) ∧ ¬ 3 2
⊽ ( o1 o2 -- ? ) or ¬ 2 3
⌈ ( x -- y ) floor 2 4
⌊ ( x -- y ) ceiling 4 6
Yes, I did make that table by hand, and yes, I did the math in my head, so there might be some, er, wrong numbers. I'll go write a program to do it for me, now :P
Treasure trove vocabularies
During my time golfing with Factor, these are the non- auto-use vocabularies that have been the most indispensable to me.
- grouping.extras - Group sequences in various ways.
- math.unicode - Shorter names for math functions, set operations, and useful combinators.
- math.combinatorics - Any time you need to get all the combinations or permutations of something, this is your vocab. Comes with tons of great combinators like
filter-permutations
for even more brevity.nCk
andinverse-permutation
come in handy sometimes too. - math.primes - Useful for prime number questions.
- math.primes.factors - Get the divisors and prime factors of a number.
- math.matrices - Matrix arithmetic, combinators and other operations.
- lists.lazy - Working with infinite lists is sometimes terser than the alternative.
- sequences.merged -
Mostly forUse2merge
, which interleaves two sequences.vmerge
inmath.vectors
instead, since it's an auto-use vocabulary. - math.text.utils -
Shortest way to get a sequence of digits from a number.This is no longer true since>dec
was added to the language. Now>dec 48 v-n
is the shortest way. - math.extras - Lots of specialized math words. You may not need them often, but when the task calls for it, there might be a built-in for it in here.
- spelling - For the
ALPHABET
word, which is the shortest way to get the alphabet on the stack. - english - Words for natural language processing.
- qw - Shorten literal sequences of strings.
- pair-rocket - Shorten literal pairs and by extension literal assocs.
- project-euler.common - Words commonly needed to solve Project Euler problems. Don't sleep on the individual problem vocabularies either! I've recently used project-euler.014 and project-euler.026 for the
collatz
andperiod-length
words.
Some useful auto-use vocabularies you may have overlooked
- math.vectors - A gem of a vocabulary. This lets you do arithmetic with a number and a sequence or two sequences. Plenty of other good stuff in here too. If you find yourself reaching for
2map
, look in here first. - math.statistics - Statistics comes up a lot in golf.
histogram
,cum-sum
,differences
,mean
, andstd
are some of my most oft-used words. There's plenty of other great stuff in there too. - combinators - Mostly for
to-fixed-point
, which applies a quotation to something until it stops changing. Avoidcase
andcond
, as they are far too verbose. - combinators.random - Conditional words based on probability instead of booleans. Extremely useful when called for.
- generalizations - Allows you to shuffle the stack in a general way.
dupn
andrepeat
have been especially helpful in golf. - sequences.generalizations - Words that can apply to any number of sequences.
narray
is also very useful for golf at times. - sequences.extras - Words here can shorten your code compared to composing
sequence
words. - sequences.deep - Traverse and flatten nested sequences.
- assocs.extras - Excellent words for working with assocs.
- interpolate - This can sometimes be shorter than
formatting
words likeprintf
.
And for those just getting started
- kernel - Stack shuffling, loops, conditionals, data flow combinators, and other critical functionality. This is the core of the language. Start here.
- math - Arithmetic.
- sequences - Collections. Factor takes the approach of having different implementations all fall under the same
sequence
type, hence the words in this vocabulary will work on most types of collections. - formatting - String interpolation.
- assocs - Associative arrays (dictionaries).
With just these five vocabs, you'll get a starting point similar to what most other languages give you.
-
1\$\begingroup\$ Self-plug for
math.matrices
that I fully rewrote and documented a year or so ago :) I was frustrated with its shortfalls from using it for math homework in high school, haha. \$\endgroup\$– catCommented Jan 15, 2022 at 3:09 -
1\$\begingroup\$ @cat Nice work on that! It's been one of the highlights of 0.99 for me. \$\endgroup\$– chunesCommented Jan 15, 2022 at 5:16
Factor is impossible to golf. If you're set on winning, don't use it.
I don't just mean it's verbose, like Java or C# or Scala. I mean, because of its functional style, there's a small number of ways to write a given program, and words are long
and whitespace is not your friend
, so it's a bad target, worse than LISP.
Factor's power is in its object model, which is highly verbose. I golf in it because I think it's a cool language, and I enjoy learning it.
The ::
and M::
words replace the normal compile word :
, so that a function has access to lexical variables.
Why use these over :
? Because often, referring to variables by name will be shorter than stack-shuffling with dup swap rot drop over nip etc
.
-
\$\begingroup\$ Ahem. Scala user here. I don't appreciate you grouping Scala with Java and C# 😠. (although you're right, all of those are nothing when compared to golfing languages) \$\endgroup\$– userCommented Jan 31, 2021 at 23:29
-
1\$\begingroup\$ @user Well, when I wrote this answer in 2016, I was under the impression Scala had verbosity comparable to Java and C#. I don't know if that's true anymore \$\endgroup\$– catCommented Feb 11, 2021 at 22:42
-
\$\begingroup\$ I was mostly kidding. Scala's got a lot less boilerplate than Java (and probably C#), but it's still somewhat verbose compared to, say, Python, in a lot of cases. I don't think it's gotten significantly more concise, though. \$\endgroup\$– userCommented Feb 11, 2021 at 23:32
You are free to trash stdout and stack under the top...
... as long as your submission is a function and the result is returned on the top of the stack.
Simple example
.
is shorter than drop
, and . .
is shorter than 2drop
. (Almost) any object that you will encounter while golfing is printable via .
, and it has the effect of removing the top item of the stack.
A real-golfing example
(a solution for Is this number a factorial?, found by golfing user's answer)
[ 1 0 [ 1 + 3dup * 3dup > ] loop . = ]
This is an extreme use of 3dup
s. It stuffs up a lot of stack items every time it is called, but it is valid (because the desired result is at the top of the stack) and correct (because it keeps the loop invariant for top three items).
Loop invariant:
( x prod idx )
1 +
( x prod idx+1 )
3dup *
( x prod idx+1 x prod' ) ! prod' = prod * (idx + 1)
3dup >
( x prod idx+1 x prod' idx+1 x>prod' )
Since loop
consumes the boolean at the top on each iteration, the top three items at the start of the next iteration are ( x prod' idx+1 )
.
When converting between numbers and strings, the
string>number
number>string
words can be shortened to:
dec>
>dec
respectively (thanks, chunes, again!).
There are also:
bin>
and>bin
for binaryoct>
and>oct
for octalhex>
and>hex
for hexadecimal
that are all shorter than n base>
, if they happen to suit your needs!
And if you need integers, use 1 /i
instead of >integer
:
5 3 /i
5 3 / >integer = .
=> t
5.77 1 /i
5.77 >integer = .
=> t
EDIT:
If it's about printing, keep an eye on these too:
.b ! prints a number in binary
.o ! prints a number in octal
.h ! prints a number in hexadecimal
-
-
1\$\begingroup\$
present
is shorter than10 >base
anddec>
is shorter than10 base>
. \$\endgroup\$– chunesCommented Mar 28, 2021 at 8:47 -
2\$\begingroup\$ Well, it finally happened ladies and gentlemen. We no longer need
present
and can use>dec
instead. github.com/factor/factor/commit/… \$\endgroup\$– chunesCommented Apr 7, 2022 at 5:32
Inline whatever you can.
All words need correct stack effect declarations in order to compile.
: hello ( -- ) "Hello, World!" print ;
These are long, even if you use single-char identifiers.
: m ( a b c d -- e f ) dup [ asd? ] bi swap = ;
Unless you use something so often that the benefits outwiegh the costs, inlining is often shorter, and : p ( a -- ) print ;
aliasing doesn't save bytes.
-
\$\begingroup\$ That's true for all languages \$\endgroup\$ Commented Mar 25, 2016 at 19:57
-
4\$\begingroup\$ @proudhaskeller in most modern languages (e.g. Python, JS, what have you), if you use
print
or whatever more than a certain number of times, you'll want to dop=print;p(...)
. In Factor, the cost for the equivalent ofp=print
is prohibitively high. \$\endgroup\$– catCommented Mar 25, 2016 at 20:01 -
\$\begingroup\$ Not being able to use a common golfing tip is not a golfing tip in itself. \$\endgroup\$ Commented Mar 25, 2016 at 20:01
-
\$\begingroup\$ Also, a general golfing tip is to inline the methods you wrote, mostly when you used the method only once (but not always), hence a general tip. \$\endgroup\$ Commented Mar 25, 2016 at 20:03
-
\$\begingroup\$ @proudhaskeller if it really bothers you, feel free to DV / flag this as NAA. \$\endgroup\$– catCommented Mar 25, 2016 at 20:03
string literals are special
I never realised this before, but "strings"
are rather special to the parser.
"asd""abc"
is equivalent to:
"asd" "abc"
and
URL" a"split
is the same as
URL" a" split
This is perhaps the only time whitespace isn't necessary in Factor, and also works for URL" "
, DLL" "
, P" "
, SBUF" "
and the others handled by parse-string
, as pointed out by @fedes. in the comments.
Note well that the opening quote-like marker must be preceded by whitespace.
The following are considered one word:
filter"asdasd"map
filter"asdasd"
And you will get a No word <blah> found in current vocabulary search path
error.
-
\$\begingroup\$ This should work for the closing
"
ofURL"
,DLL"
,P"
andSBUF"
too, as they all useparse-string
. Good find! \$\endgroup\$– fede s.Commented May 2, 2016 at 20:33 -
\$\begingroup\$ @fedes. Updated, I forgot about those! \$\endgroup\$– catCommented May 2, 2016 at 20:50
-
5\$\begingroup\$ Note this no longer works in 0.99. \$\endgroup\$– chunesCommented Apr 8, 2021 at 12:49
-
\$\begingroup\$ Because they finally fixed it? It's probably for the better. \$\endgroup\$– catCommented Apr 8, 2021 at 13:08
Get used to multiple ways to write things
Usually this means to write something using high-level combinators vs. low-level combinators and/or stack shuffle words. In some cases the former wins, in others the latter wins. Therefore, in order to push Factor golf to the limits, it is good to know both and pick the one that turns out shorter.
(Re-)Using values in the stack
For reusing single value, shuffle words usually win (because Factor has a large variety of them):
( n -- np nq) [ p ] [ q ] bi
( n -- np nq) dup p swap q
( n -- np nq nr ) [ p ] [ q ] [ r ] tri
( n -- np nq nr ) dup p over q rot r
( n -- np nq nr ns ) { [ p ] [ q ] [ r ] [ s ] } cleave
( n -- np nq nr ns ) [ p ] [ q ] [ [ r ] [ s ] bi ] tri
( n -- np nq nr ns ) dup p over q pick r roll s
But the situation is somewhat different for two or more values, because spread and apply combinators can save a copy or shuffle:
( m n -- mp nq ) [ p ] [ q ] bi*
( m n -- mp nq ) [ p ] dip q
( m n -- mp nq ) q swap p swap
( m n -- mnp mnq ) [ p ] [ q ] 2bi
( m n -- mnp mnq ) [ p ] 2keep q
( m n -- mnp mnq ) 2dup p -rot q
I can't list all possible combinations here, and one or two shuffle words can be optimized out depending on context. The lesson here is to find out multiple expressions that do the same thing stack-effect-wise.
Conditionals and loops
Factor has many ways to express some variation of if, while, and boolean logic. But it also has higher-level combinators to express various common patterns: sequences
has map
, map-index
, reduce
, filter
, partition
(I believe these are self-explanatory), accumulate
(cumulative reduce), replicate
(run n times and collect intermediate values), and produce
(run until false and collect intermediate results). combinators.to-fixed-point
runs until fixed point is reached. math.times
repeats a lambda for the given number of times.
Check if some of these high-level combinators work for the task given. If so, try to use them. Otherwise, use the simplest construct: ?
or one-branch variations of if
for a conditional, loop
for an unbounded loop, times
for simple repetition (not involving a sequence or range).
Bonus: Minimize stack item duplications and reorders
When using shuffle words, every time a shuffle word is used, you get +4~6 bytes. Try to minimize their use by duplicating multiple items at once (2dup
, 3dup
) and reordering inputs and intermediate states. Sometimes multiple library words do the same job but with different input order (e.g. each
vs. reduce
); exploit them when possible.
split
doesn't need a string
sequence as an argument
Or, more accurately / generally:
Factor strings are really just sequences of character values
Meaning string operations work on sequences of chars too
... which is important because, for instance, " " split
will split a char-array on the number 32.
More directly, this:
>string " " split
is exactly equivalent to
" " split
While being 8 chars longer.
-
\$\begingroup\$ Sometimes
"" map-as
formap >string
and family could be handy, I guess. \$\endgroup\$– fede s.Commented Apr 9, 2016 at 19:21 -
quotations are lambdas, abuse them
Most challenges can be solved with one word definition because of Factor's functional, applicative nature.
I just had the sudden realisation that [ quoting ]
code is the equivalent of a lambda definition in other languages.
I don't know why I didn't think of this before...
A normal word definition:
: f ( a c -- b ) asd asd ;
Lamba'd:
[ asd asd ]
You can do everything inside a [ quotation ]
that you can inside a :
word.
The infix
vocabulary is pretty cool.
It allows for infix notation math, which sounds lame since Factor's RPN, but:
IN: scratchpad USE: infix
IN: scratchpad [infix 5-40/10*2 infix] .
-3
For longer expressions it's much shorter than RPN because of whitespace, but you'll need to overcome the [infix
syntax's length.
It also allows Python-style slicing:
USING: arrays locals infix ; [let "foobar" :> s [infix s[0:3] infix] ] . "foo"
Additionally, you can step through sequences with seq[from:to:step] notation.
USING: arrays locals infix ; [let "reverse" :> s [infix s[::-1] infix] ] . "esrever" USING: arrays locals infix ; [let "0123456789" :> s [infix s[::2] infix] ] . "02468"
Take advantage of auto-use
Some people were already implicitly using this to discount imports. Now it is official (unless the consensus changes). No more USING: kernel math math.functions sequences ;
-- you can exclude most commonly used library imports from byte count.
Caveats:
- Actually check that the code does work with auto-use enabled. It can't load obscure libraries (e.g.
xxx.private
), and it cannot load if the word is found in multiple libraries. - If you don't have Factor installed on your machine, you can still check by entering your submission in the code section on this TIO. (Make sure the entire code has no stack effect, i.e.
( -- )
.) If it runs without error, your submission is valid.
Use linked lists when appropriate
Using linked lists is shorter than using arrays when the code involves a lot of dissection at the start (and you don't need sophisticated sequence operations):
first (5) > car (3)
rest (4) > cdr (3)
second (6) > 1 nth (5) > cadr (4)
prefix (6) > cons (4)
swap prefix (11) > swons (5)
unclip (6) = uncons (6)
unclip swap (11) > unswons (7)
Note that you don't need USE: lists
because it is auto-use
d by default.
If you see a good Haskell answer which uses pattern matching on a list, there is a good chance that a linked-list based solution is shorter in Factor too.
Use fried quotations to build sequence literals
Occasionally, you may need to build sequences with a mix of arguments and constants. It's shorter to do this
'[ 1 _ 1 ]
than this:
1 1 swapd 3array
.
quotation
is an instance of immutable-sequence
. All sequence words work on them as long as they do not mutate. For instance, you can do the following, no problem:
4 '[ 1 _ 1 ] { 3 4 5 } v+
Keep in mind many words use the type of the first argument (as is the case with v+
) as an exemplar for what type the result should be. This is usually unimportant but may occasionally be useful to know.
Here is a real-world example of when this was useful in a golf: https://codegolf.stackexchange.com/a/226034/97916
-
1\$\begingroup\$
4 '{ 1 _ 1 }
would probably be more clear ;) \$\endgroup\$– primoCommented Sep 23, 2023 at 8:46
Dip > Swap... so make a call
Idiomatic code uses quotations and dip
, but short code uses swap
. Look, you can have an extra swap per two dip
s.
[ dip ] [ dip ] [ dip ] [ dip ] [ dip ]
swap swap swap swap swap swap swap swap
However, sometimes trading swap
for dip
makes the code longer in other places, so it's a bit of a reFactoring job.
-
\$\begingroup\$ "reFactoring job" hahahahahahahahaha why is this funny \$\endgroup\$– anonCommented Apr 14, 2016 at 0:53
-
\$\begingroup\$ @QPaysTaxes I spend too much time laughing at that sort of thing, don't worry :P \$\endgroup\$– catCommented Apr 14, 2016 at 0:57
find all the higher-order functions and use them, often
Factor is full of higher-order functions that take [ quotations ]
or other-words
and work on sequences / other data.
For example, to take an IP address as a string and sum its fields, you could do:
"." split [ 10 >base ] [ + ] map-reduce 10 base>
"." split [ 10 >base ] map 0 + reduce 10 base>
"." split [ 10 >base ] map sum 10 base>
Notice that fancy map-reduce
is actually longest here because of [ quotation ]
syntax, but there's already a sum
word for that anyways, which is far shorter.
Here's a place map-reduce
is shorter:
[ "." split [ 10 base> ] [ [ 256 * ] dip + ] map-reduce ] bi@ - abs 1 + ]
[ "." split [ 10 base> ] map 0 [ [ 256 * ] dip + ] reduce ] bi@ - abs 1 + ]
Can't use sum
there.
The following are equivalent
[ 1 2 3 ] [ 1 + ] map V{ } clone-like
[ 1 2 3 ] [ 1 + ] V{ } map-as
It's just map-as
and the other -as
words (zip-as
, map-as
, accumulate-as
, etc) are usually shorter than an obj clone-like
if you're going to use them anyways.
-
\$\begingroup\$ Perhaps also worth noting that
{ } map-as
is equivalent tof map-as
. \$\endgroup\$– primoCommented Sep 23, 2023 at 8:50
The following are equivalent:
1 '[ _ blah ]
1 [ blah ] curry
It's just that the top one is shorter by three bytes.