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

带有可选字段的记录上的F#模式匹配,f#,pattern-matching,field,record,optional,F#,Pattern Matching,Field,Record,Optional,F#的“选项”似乎是使用类型系统将已知存在的数据与可能存在或不存在的数据分开的好方法,我喜欢match表达式强制考虑所有情况的方式: match str with | Some s -> functionTakingString(s) | None -> "abc" // The compiler would helpfully complain if this line wasn't present 非常有用的是,s(与str相反)是字符串而不是字符串选项 但是,

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

match str with
| Some s -> functionTakingString(s)
| None -> "abc"         // The compiler would helpfully complain if this line wasn't present
非常有用的是,
s
(与
str
相反)是
字符串而不是
字符串选项

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

type SomeRecord =
    {
        field1 : string
        field2 : string option
    }
…这些记录正在被过滤,一个
匹配
表达式感觉没有必要,因为在
的情况下没有什么明智的做法,但是

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.filter (fun record -> record.field2.IsSome)
    |> Seq.map (fun record -> functionTakingString field2)          // Won't compile
。。。不会编译,因为虽然未填充
field2
的记录已被过滤掉,
field2
的类型仍然是
string选项
,而不是
string

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

我定义了一个运算符,如果选项为
None
,则会引发异常

let inline (|?!) (value : 'a option) (message : string) =
    match value with
    | Some x -> x
    | None -> invalidOp message
…并已将以前的代码更改为此

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.filter (fun record -> record.field2.IsSome)
    |> Seq.map (fun record -> functionTakingString (record.field2 |?! "Should never happen"))           // Now compiles
…但这似乎并不理想。我可以使用
Unchecked.defaultof
而不是引发异常,但我不确定这是否更好。问题的关键是过滤后的
None
案例不相关

有没有更好的处理方法

编辑

非常有趣的答案引起了我的注意,记录模式匹配是我不知道的,而
Value
,我看到了但误解了(如果
None
,它会抛出
NullReferenceException
)。但我认为我的例子可能很糟糕,因为我更复杂的现实问题涉及使用记录中的多个字段。我怀疑我被困在像

|> Seq.map (fun record -> functionTakingTwoStrings record.field1 record.field2.Value)

除非另有说明?

在本例中,您可以使用:

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.choose (fun { field2 = v } -> v)
    |> Seq.map functionTakingString
Seq.choose
允许我们根据可选结果筛选项目。在这里,我们将在记录上进行模式匹配,以获得更简洁的代码

我认为一般的想法是使用组合器和高阶函数操纵选项值,直到您想将它们转换为其他类型的值为止(例如,在本例中使用
Seq.选择
)。使用您的
|,因为它是部分运算符(在某些情况下引发异常)。你可以说在这种特殊情况下使用它是安全的;但F#type系统无法检测到它并在任何情况下警告您不安全的使用

顺便说一句,我建议大家看看《面向铁路的编程系列》。本系列向您展示了处理错误的类型安全和可组合的方法,您可以在其中保存诊断信息

更新(编辑后):

函数的修订版本如下所示:

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

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

既然您发现了
是一些
属性,那么您可能也看到了
属性

let functionTakingSequenceOfRecords mySeq =
    mySeq
    |> Seq.filter (fun record -> record.field2.IsSome)
    |> Seq.map (fun record -> functionTakingString record.field2.Value )
有一种模式匹配的替代方案:

let functionTakingSequenceOfRecords' mySeq =
    mySeq
    |> Seq.choose (function
        | { field2 = Some v } ->  functionTakingString v |> Some
        | _ -> None )

我解释的问题是,您希望类型系统始终反映集合中的那些记录实际上在field2中包含字符串这一事实

我的意思是,当然你可以使用
选择
来过滤掉那些你不在乎的记录,但是你最终还是会得到一个带有可选字符串的记录集合,你知道所有这些记录都是字符串

一种替代方法是创建一个通用记录,如下所示:

type SomeRecord<'T> =
{
    field1 : string
    field2 : 'T
}
因此,这里忽略field2的内容,而是使用元组中的第一个值


除非我误解了您的问题,您不再关心模式匹配,或者使用警告或#nowarn指令进行不完全匹配,或者使用选项的
.Value
属性,如其他答案所示。

谢谢您的建议
.Value
-我看到了,但被误解了。我喜欢记录模式匹配,但根据我问题中的编辑,我不确定我是否可以使用它。除非……:o) @Giles为了使匹配更加通用,您可以根据需要引入更多的
记录模式
,或者使用
“as”模式
将整个模式绑定到一个变量(例如
|{field2=Some v}as record->…
)。多亏了另一个指针,我将阅读“as”模式。我在F#上的第一周,我意识到还有很多事情要知道,比我目前学到的要多得多!谢谢你非常有趣的回答。根据我上面问题中的编辑,我不确定是否可以使用记录模式匹配,但也感谢面向铁路的编程链接。@Giles:我根据您的编辑修改了我的答案。我认为我的总体想法仍然有效。您应该努力使用类型安全的方法来转换值。非常感谢您的更新。我可能需要一点时间才能完全消化,但我想我明白要点了!公平地说,潜在的缺点是,对于包含许多字段的记录,有必要按照v1和v2绑定所有字段吗?是的。在这种情况下,也许计算表达式会很方便使用。谢谢-通用记录和元组方法都是有趣的替代方法。
let functionTakingSequenceOfRecords mySeq =
    let getField2 record = 
        match record with
        | {field2 = Some value} -> Some (value, record)
        | _ -> None

    mySeq
    |> Seq.choose getField2
    |> Seq.map (fun (f2, {field1 = f1}) -> functionTakingTwoStrings f1 f2)