I think your approach is generally good. There was an answer that is now deleted that suggested to use try/with to prevent double-evaluation of the first item by catching the error for empty sequences, but that too can be expensive.
If you want to prevent double evaluation, you can use Seq.cache, or not use Seq at all (use List or Array instead). Or use fold, which iterates only once:
module Seq =
let tryMin sq =
sq
|> Seq.fold(fun x y ->
match x with None -> Some y | Some x -> Some(min x y)) None
Usage:
> Seq.tryMin Seq.empty<int>;;
val it : int option = None
> Seq.tryMin (Seq.singleton 2L);;
val it : int64 option = Some 2L
> Seq.tryMin (seq { 2; 3});;
val it : int option = Some 2
> Seq.tryMin (seq { 2; -3});;
val it : int option = Some -3
A potentially faster method (I didn't time it), is to prevent the creation of option on each min- or max-calculation result, and at the same time preventing multiple iterations of the first item.
This should have much less GC pressure too ;).
module Seq =
let tryMin (sq: seq<_>) =
use e = sq.GetEnumerator()
// this returns false if there is no first item
if e.MoveNext() then
let mutable result = e.Current
while e.MoveNext() do
result <- min e.Current result
Some result
else
None
Usage:
> Seq.tryMin Seq.empty<int>;;
val it : int option = None
> Seq.tryMin (Seq.singleton 2L);;
val it : int64 option = Some 2L
> Seq.tryMin (seq { 2; 3});;
val it : int option = Some 2
> Seq.tryMin (seq { 2; -3});;
val it : int option = Some -3