21
\$\begingroup\$

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

\$\endgroup\$

18 Answers 18

14
\$\begingroup\$

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

\$\endgroup\$
10
\$\begingroup\$

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 and inverse-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 for 2merge, which interleaves two sequences. Use vmerge in math.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 and period-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, and std 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. Avoid case and cond, 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 and repeat 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 like printf.

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.

\$\endgroup\$
2
  • 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\$
    – cat
    Commented 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\$
    – chunes
    Commented Jan 15, 2022 at 5:16
6
\$\begingroup\$

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.

The docs on lexical vars.

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.

\$\endgroup\$
3
  • \$\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\$
    – user
    Commented 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\$
    – cat
    Commented 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\$
    – user
    Commented Feb 11, 2021 at 23:32
6
\$\begingroup\$

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 . = ]

Try it online!

This is an extreme use of 3dups. 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 ).

\$\endgroup\$
5
\$\begingroup\$

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 binary
  • oct> and >oct for octal
  • hex> 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
\$\endgroup\$
3
  • \$\begingroup\$ (psst!) \$\endgroup\$
    – cat
    Commented Apr 24, 2016 at 2:38
  • 1
    \$\begingroup\$ present is shorter than 10 >base and dec> is shorter than 10 base>. \$\endgroup\$
    – chunes
    Commented 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\$
    – chunes
    Commented Apr 7, 2022 at 5:32
4
\$\begingroup\$

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.

\$\endgroup\$
5
  • \$\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 do p=print;p(...). In Factor, the cost for the equivalent of p=print is prohibitively high. \$\endgroup\$
    – cat
    Commented 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\$
    – cat
    Commented Mar 25, 2016 at 20:03
4
\$\begingroup\$

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.

\$\endgroup\$
4
  • \$\begingroup\$ This should work for the closing " of URL", DLL", P" and SBUF" too, as they all use parse-string. Good find! \$\endgroup\$
    – fede s.
    Commented May 2, 2016 at 20:33
  • \$\begingroup\$ @fedes. Updated, I forgot about those! \$\endgroup\$
    – cat
    Commented May 2, 2016 at 20:50
  • 5
    \$\begingroup\$ Note this no longer works in 0.99. \$\endgroup\$
    – chunes
    Commented Apr 8, 2021 at 12:49
  • \$\begingroup\$ Because they finally fixed it? It's probably for the better. \$\endgroup\$
    – cat
    Commented Apr 8, 2021 at 13:08
4
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ Sometimes "" map-as for map >string and family could be handy, I guess. \$\endgroup\$
    – fede s.
    Commented Apr 9, 2016 at 19:21
  • \$\begingroup\$ @fedes. Ooh, that's a good one \$\endgroup\$
    – cat
    Commented Apr 11, 2016 at 2:49
3
\$\begingroup\$

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.

\$\endgroup\$
0
3
\$\begingroup\$

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"
\$\endgroup\$
3
\$\begingroup\$

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.
\$\endgroup\$
3
\$\begingroup\$

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

\$\endgroup\$
3
\$\begingroup\$

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

\$\endgroup\$
1
  • 1
    \$\begingroup\$ 4 '{ 1 _ 1 } would probably be more clear ;) \$\endgroup\$
    – primo
    Commented Sep 23, 2023 at 8:46
2
\$\begingroup\$

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

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

\$\endgroup\$
2
  • \$\begingroup\$ "reFactoring job" hahahahahahahahaha why is this funny \$\endgroup\$
    – anon
    Commented Apr 14, 2016 at 0:53
  • \$\begingroup\$ @QPaysTaxes I spend too much time laughing at that sort of thing, don't worry :P \$\endgroup\$
    – cat
    Commented Apr 14, 2016 at 0:57
2
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ Perhaps also worth noting that { } map-as is equivalent to f map-as. \$\endgroup\$
    – primo
    Commented Sep 23, 2023 at 8:50
1
\$\begingroup\$

The following are equivalent:

1 '[ _ blah ]
1 [ blah ] curry

It's just that the top one is shorter by three bytes.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.