开发者

Optionally taking the first item in a sequence

开发者 https://www.devze.com 2023-04-06 17:31 出处:网络
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.

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.Cu开发者_StackOverflow社区rrent
  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.


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


You could define:

let seqTryHead s = Seq.tryPick Some s

It is of type seq<'a> -> 'a option. Note that I don't beta-reduce because of the generic value limitation.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号