带有可选字段的记录上的F#模式匹配

Gil*_*les 5 f# field record pattern-matching optional

F#的'选项'似乎是一种很好的方式,使用类型系统将已知存在的数据与可能存在或不存在的数据分开,我喜欢match表达式强制执行所有情况的方式:

match str with
| Some s -> functionTakingString(s)
| None -> "abc"         // The compiler would helpfully complain if this line wasn't present
Run Code Online (Sandbox Code Playgroud)

s(反对str)是一个string而不是一个非常有用string option.

但是,在处理具有可选字段的记录时...

type SomeRecord =
    {
        field1 : string
        field2 : string option
    }
Run Code Online (Sandbox Code Playgroud)

......那些记录正在被过滤,match表达感觉不必要,因为在这种None情况下没有任何明智的做法,但是......

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.filter (fun record -> record.field2.IsSome)
    |> Seq.map (fun record -> functionTakingString field2)          // Won't compile
Run Code Online (Sandbox Code Playgroud)

...将无法编译,因为虽然field2已经过滤掉未填充的记录,但类型field2仍然是string option,但不是string.

我可以定义另一种记录类型,其中field2不是可选的,但这种方法似乎很复杂,并且可能无法使用许多可选字段.

我已经定义了一个运算符,如果一个选项是None...... 就会引发异常......

let inline (|?!) (value : 'a option) (message : string) =
    match value with
    | Some x -> x
    | None -> invalidOp message
Run Code Online (Sandbox Code Playgroud)

...并将之前的代码更改为此...

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.filter (fun record -> record.field2.IsSome)
    |> Seq.map (fun record -> functionTakingString (record.field2 |?! "Should never happen"))           // Now compiles
Run Code Online (Sandbox Code Playgroud)

......但它似乎并不理想.我可以使用Unchecked.defaultof而不是提出异常,但我不确定那更好.问题的关键在于None过滤后案例不相关.

有没有更好的方法来处理这个?

编辑

非常有趣的答案带来了记录模式匹配我的注意力,我不知道,并且Value,我看到但误解了(我看到它抛出一个NullReferenceExceptionif None).但我认为我的例子可能很差,因为我更复杂,现实生活中的问题涉及从记录中使用多个字段.我怀疑我被困在......

|> Seq.map (fun record -> functionTakingTwoStrings record.field1 record.field2.Value)
Run Code Online (Sandbox Code Playgroud)

除非有其他的东西?

pad*_*pad 4

在此示例中,您可以使用:

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.choose (fun { field2 = v } -> v)
    |> Seq.map functionTakingString
Run Code Online (Sandbox Code Playgroud)

Seq.choose允许我们根据可选结果过滤项目。在这里,我们对记录进行模式匹配以获得更简洁的代码。

我认为总体思路是使用组合器、高阶函数来操作选项值,直到您想将它们转换为其他类型的值(例如Seq.choose在本例中使用)。不鼓励使用 your ,|?!因为它是部分运算符(在某些情况下抛出异常)。您可以说在这种特殊情况下使用它是安全的;但 F# 类型系统无法检测到它,并在任何情况下警告您有关不安全使用的信息。

顺便说一句,我建议您看看http://fsharpforfunandprofit.com/posts/recipe-part2/上的面向铁路的编程系列。该系列向您展示了处理错误的类型安全且可组合的方法,您可以在其中保留诊断信息。

更新(根据您的编辑):

您的函数的修订版本编写如下:

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.choose (fun { field1 = v1; field2 = v2 } -> 
         v2 |> Option.map (functionTakingString v1))
Run Code Online (Sandbox Code Playgroud)

它演示了我提到的一般思想,即使用高阶函数 ( Option.map) 操作选项值并在最后一步 ( Seq.choose) 转换它们。