Similar to this question - what is the most idiomatic way to write LINQ's SingleOrDefault in F#?
Similar to this question - what is the most idiomatic way to write LINQ's SingleOrDefault in F#?
If you want the function to return null (or default for value types) when the sequence is empty, just go ahead and call the existing SingleOrDefault method. You can call C# methods from F# just fine. Keep in mind, however, that most F# native types do not admit nulls, so this may not always be possible.
If you want your function to return an option type, falling back to None when the sequence contains zero elements or more than one, you can use Seq.truncate to truncate to the first two elements:
let singleOrDefault s =
match s |> Seq.truncate 2 |> Seq.toList with
| [x] -> Some x
| _ -> None
Or similarly for the overload that also takes a predicate:
let singleOrDefaultP pred s =
match s |> Seq.filter pred |> Seq.truncate 2 |> Seq.toList with
| [x] -> Some x
| _ -> None
If you prefer having both versions around, it probably makes sense to express one in terms of the other to keep things DRY:
let singleOrDefault s = singleOrDefaultP (fun _ -> true) s
The advantage of this solution over using an additional Seq.isEmpty check is that the sequence is only evaluated once, and only as far as it needs to.
Incidentally, this is exactly what the default implementation of SingleOrDefault does.
Also note that this solution will return None when the sequence contains more than one element, instead of throwing an exception, as the original does. This is preferred, idiomatic way of dealing with errors in F#. However, if you prefer the original way, it can be easily achieved by adding another case to the match:
let singleOrDefault s =
match s |> Seq.truncate 2 |> Seq.toList with
| [x] -> Some x
| [_;_] -> failWith "Too many" // replace with your favourite exception
| _ -> None
If you need the exact behaviour of SingleOrDefault, I suggest the following:
module Seq
open System.Linq
let singleOrDefault (s : 'x seq) =
s.SingleOrDefault ()
Otherwise, see your own answer for a perfectly sensible solution.
My take is to have some SequenceExtensions.fs with
module Seq
let trySingle = function
| seq when seq |> Seq.isEmpty ->
None
| seq ->
seq
|> Seq.exactlyOne
|> Some
To be used elsewhere like [42] |> Seq.trySingle.