Tips for golfing in Factor

7

3

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

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

Answers

4

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

That's true for all languages – proud haskeller – 2016-03-25T19:57:04.860

3@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. – cat – 2016-03-25T20:01:07.067

Not being able to use a common golfing tip is not a golfing tip in itself. – proud haskeller – 2016-03-25T20:01:55.483

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. – proud haskeller – 2016-03-25T20:03:05.150

@proudhaskeller if it really bothers you, feel free to DV / flag this as NAA. – cat – 2016-03-25T20:03:17.410

4

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

4

When converting between numbers and strings, the

string>number
number>string

words can be shortened to:

10 base>
10 >base

respectively. Saves 5 chars each.

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

fede s.

Posted 2016-03-16T01:42:50.387

Reputation: 945

(psst!) – cat – 2016-04-24T02:38:30.217

4

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

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

3

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

Sometimes "" map-as for map >string and family could be handy, I guess. – fede s. – 2016-04-09T19:21:11.170

@fedes. Ooh, that's a good one – cat – 2016-04-11T02:49:28.080

3

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

3

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"

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

2

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

"reFactoring job" hahahahahahahahaha why is this funny – Fund Monica's Lawsuit – 2016-04-14T00:53:52.687

@QPaysTaxes I spend too much time laughing at that sort of thing, don't worry :P – cat – 2016-04-14T00:57:44.857

2

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

2

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

This should work for the closing " of URL", DLL", P" and SBUF" too, as they all use parse-string. Good find! – fede s. – 2016-05-02T20:33:19.810

@fedes. Updated, I forgot about those! – cat – 2016-05-02T20:50:19.707

1

The following are equivalent:

1 '[ _ blah ]
1 [ blah ] curry

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

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989

1

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.

cat

Posted 2016-03-16T01:42:50.387

Reputation: 4 989