Categories
Tech

F# Friday – List.tryHead

Sometimes you need to get the first element of a list. No problem: List.head to the rescue, right? But what happens when you call List.head on an empty list?

let xs : int list = []
let h = xs |> List.head
// System.ArgumentException: The input list was empty.

Well that’s not good. You could get around that little wart with this:

let xs : int list = []
let h = if xs |> List.isEmpty 
        then -1
        else xs |> List.head
// val h : int = -1

That’s not great. Alternatively, there’s this:

let xs : int list = []
let h = match xs with
        | h :: _ -> h
        | [] -> -1
// val h : int = -1

Yeah, I’m not wild about those options either.

Speaking of options, what if you had a method that returns a None if you ask for the head of an empty list? If the list is not empty, it could return a Some containing the value of the head element. Another new method in F# 4.0 is List.tryHead, and it does just that.

let empty : int list = []
let nonempty = [9..17]

let nuthin = empty |> List.tryHead
// val nuthin : int option = None

let sumthin = nonempty |> List.tryHead
// val sumthin : int option = Some 9

Now you can use defaultArg on the result of a call to List.tryHead in order to return a default value in the event of an empty list:

let empty : int list = []
let nonempty = [9..17]

let head = defaultArg (List.tryHead nonempty) -1
// val head : int = 9

let fallback = defaultArg (List.tryHead empty) -1
// val fallback : int = -1

Now when might you actually use something like this? Perhaps you want to determine the top salesman each day, but only if the salesman has reached a certain threshold, say, $10,000. You can filter out the salesmen who don’t reach the threshold, sort the list of salesmen according to end-of-day sales totals, and then try to take the head element. If no one makes the cut, then the filter operation returns an empty list, which ultimately yields a None.

type Salesman = { Name : string
                  Sales : decimal }

let findTopSalesman salesmen =
    salesmen
    |> List.filter (fun s -> s.Sales >= 10000M)
    |> List.sortBy (fun s -> -s.Sales) // descending
    |> List.tryHead

So then, if Monday’s sales are as follows, then no one gets the prize because no one has broken $10,000:

let monday = 
    [
        { Name = "Joe Bob"; Sales = 9500M }
        { Name = "Sally Jane"; Sales = 8500M }
        { Name = "Betty Lou"; Sales = 9800M }
        { Name = "Sammy Joe"; Sales = 6500M }
    ]

let mondayTop = findTopSalesman monday
// val mondayTop : Salesman option = None

On Tuesday, though, there are two contenders. Alas, there can be only one winner, and Sally Jane (who, ironically, is from British Columbia) takes the prize:

let tuesday =
    [
        { Name = "Joe Bob"; Sales = 9500M }
        { Name = "Sally Jane"; Sales = 18500M }
        { Name = "Betty Lou"; Sales = 11800M }
        { Name = "Sammy Joe"; Sales = 6500M }
    ]

let tuesdayTop = findTopSalesman tuesday
// val tuesdayTop : Salesman option = 
//   Some {Name = "Sally Jane"; Sales = 18500M;}

3 replies on “F# Friday – List.tryHead”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.