Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optionally taking the first item in a sequence

I need a function like Seq.head, but returning None instead of throwing an exception when the sequence is empty, i.e., seq<'T> -> 'T option.

There are a jillion ways to do this. Here are several:

let items = Seq.init 10 id
let a = Seq.tryFind (fun _ -> true) items
let b = Seq.tryPick Some items
let c = if Seq.isEmpty items then None else Some (Seq.head items)
let d = 
  use e = items.GetEnumerator()
  if e.MoveNext() then Some e.Current
  else None

b is the one I use. Two questions:

  1. Is there a particularly idiomatic way to do this?
  2. Since there's no built-in Seq.tryHead function, does that indicate this shouldn't be necessary, is uncommon, or is better implemented without a function?

UPDATE

tryHead has been added to the standard library in F# 4.0.
like image 600
Daniel Avatar asked Sep 20 '11 15:09

Daniel


1 Answers

I think (b) is probably the most idiomatic, for the same reason @Ramon gave.

I think the lack of Seq.tryHead just means that it is not super common.

I'm not sure, but my guess is that functional languages with Hindley-Milner type inference in general are sparse about implementing such specific functions on collection types because overloading isn't available and composing higher-order functions can be done tersely.

For example, C# Linq extensions are much more exhaustive than functions in F#'s Seq module (which itself is more exhaustive than functions on concrete collection types), and even has IEnumerable.FirstOrDefault. Practically every overload has a variation which performs a map.

I think emphasis on pattern matching and concrete types like list is also a reason.

Now, most of the above is speculation, but I think I may have a notion closer to being objective. I think a lot of the time tryPick and tryFind can be used in the first place instead of filter |> tryHead. For example, I find myself writing code like the following fairly frequently:

open System.Reflection
let ty = typeof<System.String> //suppose this type is actually unknown at compile time
seq {
    for name in ["a";"b";"c"] do
        yield ty.GetMethod(name)
} |> Seq.tryFind((<>)null)

instead of like

...
seq {
    for name in ["a";"b";"c"] do
        match ty.GetMethod(name) with
        | null -> ()
        | mi -> yield mi
} |> tryHead
like image 88
Stephen Swensen Avatar answered Nov 14 '22 04:11

Stephen Swensen