F# 中的 SingleOrDefault

SingleOrDefault in F#

类似于 F# 中的 this question - what is the most idiomatic way to write LINQ's SingleOrDefault

我的做法是 SequenceExtensions.fs

module Seq 

let trySingle = function
    | seq when seq |> Seq.isEmpty ->
        None
    | seq ->
        seq
        |> Seq.exactlyOne
        |> Some

用于其他地方,如 [42] |> Seq.trySingle

如果您需要 SingleOrDefault 的确切行为,我建议如下:

module Seq

open System.Linq

let singleOrDefault (s : 'x seq) =
    s.SingleOrDefault ()

否则,请查看您自己的答案以获得完全明智的解决方案。

如果您希望函数在序列为空时 return null(或值类型的默认值),只需继续调用现有的 SingleOrDefault 方法即可。您可以很好地从 F# 调用 C# 方法。但是请记住,大多数 F# 本机类型不允许空值,因此这可能并不总是可行的。

如果你想让你的函数return一个选项类型,当序列包含零个或多个元素时回落到None,你可以使用Seq.truncate截断到前两个元素:

let singleOrDefault s =
    match s |> Seq.truncate 2 |> Seq.toList with
    | [x] -> Some x
    | _ -> None

或者对于同样采用谓词的重载类似地:

let singleOrDefaultP pred s =
    match s |> Seq.filter pred |> Seq.truncate 2 |> Seq.toList with
    | [x] -> Some x
    | _ -> None

如果您更喜欢同时使用两个版本,用另一个版本来表达一个版本可能是有意义的,这样可以保持干爽:

let singleOrDefault s = singleOrDefaultP (fun _ -> true) s

与使用额外的 Seq.isEmpty 检查相比,此解决方案的优势在于序列仅被评估一次,并且仅在需要时被评估。

顺便说一句,这正是 the default implementation of SingleOrDefault 所做的。

另请注意,当序列包含多个元素时,此解决方案将 return None,而不是像原始解决方案那样抛出异常。这是在 F# 中处理错误的首选、惯用的方法。但是,如果您更喜欢原始方式,可以通过在匹配中添加另一个案例来轻松实现:

let singleOrDefault s =
    match s |> Seq.truncate 2 |> Seq.toList with
    | [x] -> Some x
    | [_;_] -> failWith "Too many"  // replace with your favourite exception
    | _ -> None