12
\$\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\$

17 Answers 17

9
\$\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\$
5
\$\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 Jan 31 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 Feb 11 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 Feb 11 at 23:32
5
\$\begingroup\$

When converting between numbers and strings, the

string>number
number>string

words can be shortened to:

dec>
present

respectively (thanks, chunes).

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\$
2
  • \$\begingroup\$ (psst!) \$\endgroup\$ – cat Apr 24 '16 at 2:38
  • 1
    \$\begingroup\$ present is shorter than 10 >base and dec> is shorter than 10 base>. \$\endgroup\$ – chunes Mar 28 at 8:47
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\$ – proud haskeller Mar 25 '16 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 Mar 25 '16 at 20:01
  • \$\begingroup\$ Not being able to use a common golfing tip is not a golfing tip in itself. \$\endgroup\$ – proud haskeller Mar 25 '16 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\$ – proud haskeller Mar 25 '16 at 20:03
  • \$\begingroup\$ @proudhaskeller if it really bothers you, feel free to DV / flag this as NAA. \$\endgroup\$ – cat Mar 25 '16 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. May 2 '16 at 20:33
  • \$\begingroup\$ @fedes. Updated, I forgot about those! \$\endgroup\$ – cat May 2 '16 at 20:50
  • 1
    \$\begingroup\$ Note this no longer works in 0.99. \$\endgroup\$ – chunes Apr 8 at 12:49
  • \$\begingroup\$ Because they finally fixed it? It's probably for the better. \$\endgroup\$ – cat Apr 8 at 13:08
4
\$\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\$
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. Apr 9 '16 at 19:21
  • \$\begingroup\$ @fedes. Ooh, that's a good one \$\endgroup\$ – cat Apr 11 '16 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\$
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\$ – Fund Monica's Lawsuit Apr 14 '16 at 0:53
  • \$\begingroup\$ @QPaysTaxes I spend too much time laughing at that sort of thing, don't worry :P \$\endgroup\$ – cat Apr 14 '16 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\$
2
\$\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\$
2
\$\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\$
2
\$\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\$
1
\$\begingroup\$

The following are equivalent:

1 '[ _ blah ]
1 [ blah ] curry

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

\$\endgroup\$
1
\$\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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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