25
\$\begingroup\$

What general tips do you have for golfing in F#? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to F# (e.g. "remove comments" is not an answer). Please post one tip per answer.

\$\endgroup\$

13 Answers 13

10
\$\begingroup\$

Need to provide a type annotation for a variable so that you can call a method on it? Just compare it against a literal of the type you want it to be then throw away the result:

let f (x:string)=x.Length
let f x=x="";x.Length
\$\endgroup\$
9
\$\begingroup\$

Use function instead of match when possible; it'll save 6 characters for 1-character variable names:

let f=function // ... (14 chars)

vs

let f x=match x with // ... (20 chars)

It can also replace any pattern match to consistently save 1 character:

match a with|          // ... (13 chars)
a|>function|           // ... (12 chars)
(function| (* ... *))a // (12 chars)
\$\endgroup\$
8
\$\begingroup\$

Use the prefix notation for infix operators when you can - it'll save you from having to define a function to use them.

For example, you can turn this:

List.map(fun i->i+2)[1;1;2;3;5;8]

into this:

List.map((+)2)[1;1;2;3;5;8]
\$\endgroup\$
1
  • 2
    \$\begingroup\$ I use it here thank you! \$\endgroup\$
    – aloisdg
    Commented Nov 3, 2017 at 23:44
6
\$\begingroup\$

Tuple deconstruction

In case you can't get around to using variables, use tuple deconstruction instead of multiple let expressions

let a,b ="",[]

instead of

let a=""
let b=[]

Reading from stdin

F# core library defines an alias for System.Console.In called stdin. These allow you to read input.

// Signature:
stdin<'T> :  TextReader

TextReader on msdn

The big advantage aside the fact that it's shorter than Console is, you don't have to open System either

Iterating over string

string is basically a char seq, this allows you to use Seq.map directly with strings. It's also possible to use them in comprehensions [for c in "" do]

Mutables/Reference cells

Using reference cells is not always shorter as every read operation comes with an additional character to deref the cell.

General tips

  • It is possible to write the complete match .. with inline

    function|'a'->()|'b'->()|_->()
    
  • There is no need for white-space before and after non alphanumeric characters.

    String.replicate 42" "
    if Seq.exists((<>)'@')s then
    if(Seq.exists((<>)'@')s)then
    
  • In case you need to left or right pad a string with spaces, you can use [s]printf[n] flags for that.

    > sprintf "%20s" "Hello, World!";;
    val it : string = "       Hello, World!"
    

    Core.Printf Module

\$\endgroup\$
4
\$\begingroup\$

Use id instead of x->x

id is an operator standing for the identity function.

let u x=x|>Seq.countBy (fun x->x)

can be written

let u x=x|>Seq.countBy id

source

I use it here

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

Eta-conversion for functions

Many thanks to Laikoni for this tip in one of my solutions.

Consider a function to, say, sum a string with 3 for upper-case letters and 1 for all other characters. So:

let counter input = Seq.sumBy (fun x -> if Char.IsUpper x then 3 else 1) input

By eta-conversion this can be re-written as:

let counter = Seq.sumBy (fun x -> if Char.IsUpper x then 3 else 1)

and called in the same way as before:

counter "Hello world!" |> printfn "%i"

The function forward-composition operator >>

Now suppose our original challenge would be to sum a string with 3 for upper-case letters and 1 for lower-case letters, and all other characters are excluded.

We might write this as:

let counter input = Seq.filter Char.IsLetter input |> Seq.sumBy (fun x -> if Char.IsUpper x then 3 else 1)

We can use the forward-composition operator (>>) to chain the two functions (Seq.filter and Seq.sumBy) together. With eta-conversion the function definition would become:

let counter = Seq.filter Char.IsLetter >> Seq.sumBy (fun x -> if Char.IsUpper x then 3 else 1)

Chris Smith did a great write-up on the >> operator on his MSDN blog.

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

Prefer new line string over "\n"

This will start to pay off at even a single new line character in your code. One use case might be:

(18 bytes)

string.Concat"\n"

(17 bytes)

string.Concat"
"

Inspired from Chiru's answer for es6.

Used here

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

Use .NET

.NET offers a lot of nice builtins. F# can use them, so dont forget them!

Example:

open System.Linq

It can be helpful!

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

When possible Seq is shorter than List:

[[1];[2;3];[4];[5]|>List.collect
[[1];[2;3];[4];[5]|>Seq.collect

is one char shorter...

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

Avoid parenthesis when using one parameter and on tuple

let f = [(0,1);(1,4)]|>Seq.map(fst)
printfn "%A" f

can be written

let f = [0,1;1,4]|>Seq.map fst
printfn "%A" f
\$\endgroup\$
1
  • 2
    \$\begingroup\$ You also don't need () around tuples: let f=[0,1;1,4]|>Seq.map fst \$\endgroup\$ Commented Feb 19, 2019 at 13:32
2
\$\begingroup\$

Use for...to instead of for...in to walk a range

for i in[0..2]
for i=0 to 2
\$\endgroup\$
2
\$\begingroup\$

The module keyword can be used to shorten module names when used repeatedly. For example:

Array.fold ...
Seq.iter ...
List.map ...

can become

module A=Array
A.fold ...
module S=Seq
S.iter ...
module L=List
L.map ...

This is more useful for longer programs where module methods are used repeatedly (and must be fully named each time because they have the RequireQualifiedAccess modifier), and allows shaving a few chars off especially when it's more useful to use a regular CLR array (e.g., mutability) than an F# seq or list.

\$\endgroup\$
1
\$\begingroup\$

Use lambdas to save a byte. For example, this:

let f x=x*x

Can be expressed as this:

fun x->x*x
\$\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.