首页 > 解决方案 > SingleOrDefault in F#

问题描述

Similar to this question - what is the most idiomatic way to write LINQ's SingleOrDefault in F#?

标签: linqf#c#-to-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

推荐阅读