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”
[…] week, we looked at List.tryHead. If you don’t need the first item in a list, but rather the last item, the counterpart of […]
[…] F# Friday – List.tryHead – Brad Collins […]
[…] F# Friday – List.tryHead via Brad Collins […]