Skip to content

F# Mistakes

Louis Kueh edited this page Jan 17, 2019 · 3 revisions

Learning examples

let retainPositive lst = List.collect (fun a -> if a > 0 then a else []) lst
    retainPositive [ 1; -2; -4; 0; 11] // should return [1; 11]
  • recording types of the function output and check if they are correct. - Ionide already somewhat does this?
let intList = [1..5]
let makePairs a0 = List.map (fun b -> (a0,b)) intList
let pairList = List.collect makePairs intList
// Use anon functions to avoid baked in values
let makePairs a0 = fun lst -> List.map (fun b -> (a0,b)) lst
let pairList = fun lst -> List.collect (fun a0 -> makePairs a0 lst) lst

Partially applied functions:

Think about which one will be most likely be (partially applied)[https://intranet.ee.ic.ac.uk/t.clarke/hlp/tutor1/A7.html] must be written as the first parameter

let intList = [1..5]
let makePairs lst = fun a -> List.map (fun x -> (a,x)) lst 
let pairList = List.map (makePairs intList) intList
printfn "%A" pairList
let square (x) =
    x*x
let makeTriple (a,b) = (square(a) - square(b),2 * a * b,square(a) + square(b)) 
let tripleList = List.map makeTriple pairList

printfn doesn't print, only outputs error?

let pairList = List.collect (fun a -> List.map (fun b -> (a,b)) [1..5]) [1..5]
let makeTriple (a,b) =
    let sq x = x*x
    (sq a - sq b, 2*a*b, sq a + sq b)
let tripleList = List.map makeTriple pairList
printfn "Triples are:%A" tripleList
System.Console.ReadLine() // prevent the program from terminating in Visual Studio

// versus

// this code is clever but not as easy to read as when makePairs is named
let pairListFn = fun lst -> List.collect (fun a -> List.map (fun x -> (a,x)) lst) lst
let makeTriple (a,b) =
    let sq x = x*x
    (sq a - sq b, 2*a*b, sq a + sq b)
let tripleList = List.map makeTriple (pairListFn [1..5])
printfn "Triples are:%A" tripleList
System.Console.ReadLine() // prevent the program from terminating
[<fun:Invoke@2810>; <fun:Invoke@2810>; <fun:Invoke@2810>; <fun:Invoke@2810>;
   <fun:Invoke@2810>; <fun:Invoke@2810>; <fun:Invoke@2810>; <fun:Invoke@2810>;
   <fun:Invoke@2810>;

Simplify anon function

let singleList  =  fun a -> (a, makeTriple a)
let multiList  = fun lst -> List.map (fun a -> singleList a)  lst
//versus
let multiList pl = List.map (fun p -> p, makeTriple p) pl
  • Empty list/array: [] is an empty list and also an empty list of lists, while [[]] is a list of lists containing only one empty list as its elements.

  • List/array delimiter: While ; is the list/array delimiter; , is the tuple delimiter. Therefore, [1, 3, 2, 4] is an (intintint*int) list with one element and [1; 3; 2; 4] is an int list with 4 elements.

  • Incomplete if/else expression: Any function returning unit can be an incomplete if/else statement: List.iter (fun i -> if i%2=0 then printfn "i=%i" i) [1; 3; 2; 4] However, other functions require a complete if...then...else... expression: List.map (fun i -> if i%2=0 then i+1 else i) [1; 3; 2; 4]

let func = 
    printfn "hi"
    1

// vs.

let func() = 
    printfn "hi"
    1

Explanation

Sometimes, you want to define a function that doesn't take any input. You can't, however, define it like this:

let f = whatever

because that would make it a value that's immediately let-bound to whatever. Instead, you can let the function take a value of the built-in type unit. This type only has a single value, which is written ():

let f () = whatever

This means that f is a function that pattern matches its input against the only known value of unit.

Whenever you invoke f with (), the expression whatever is evaluated and returned.

Asynchronous workflows

Another common error related to asynchronous workflows (especially when using MailboxProcessor) is to use do! to make recursive calls. The following code is wrong and it leaks memory with every loop:

let rec loop () = async {
  do! Async.Sleep(1000)
  do! loop() } // Wrong!

// In order to write tail-recursive calls in F# async workflows, we should use return!:

let rec loop () = async {
  do! Async.Sleep(1000)
  return! loop() } // Correct :-)

Repeated indents

The most common mistakes I see are generally related to scoping stuff incorrectly, generally creating scopes that just have too much stuff in them. Over use of shadowing, that is reusing an identifier name, can lead to confusing code. Generally this feature is fine if used in small scope, but can be very make code difficult to understand if used in a large scope. For example, if the dots are replaced with an arbitary amount of valid code the following example would be hard to follow as it would be difficult to know which version of myIdent is in scope:

let main() =
    let myIdent = "something ..."
    ...
    ...
    ...
    let myFunc() =
        let myIdent = "something else ..."
        ...
        ...
        myIdent
    ...
    myIdent

Union matching

type TickTack = Tick | Tack
 
let ticker x =
    match x with
    | Tick -> printfn "Tick"
    | Tock -> printfn "Tock"
    | Tack -> printfn "Tack"
 
ticker Tick
ticker Tack

Ouput is

Tick
Tock

Instead of expected

Tick
Tack

While Tick and Tack are union cases of the type TickTack, Tock is a value of type TickTack.

When Tack is entered as an argument to ticker function pattern rule 1 does not match, but pattern rule 2 instead of any matching bind Tack to Tock then executes "Tock".

Clone this wiki locally