F#的“选项”似乎是一种很好的利用类型系统将已知存在的数据与可能存在或不存在的数据分开的方式,我喜欢match表达式强制考虑所有情况的方式:
match str with
| Some s -> functionTakingString(s)
| None -> "abc" // The compiler would helpfully complain if this line wasn't present
s
(与str
相反)作为string
而不是string option
非常有用。
然而,在处理具有可选字段的记录时......
type SomeRecord =
{
field1 : string
field2 : string option
}
当这些记录被过滤时,match
表达式显得有些多余,因为在None
情况下没有明智的操作,但是这...
let functionTakingSequenceOfRecords mySeq =
mySeq
|> Seq.filter (fun record -> record.field2.IsSome)
|> Seq.map (fun record -> functionTakingString field2) // Won't compile
这段代码无法编译,因为虽然已经过滤掉了没有填充field2
的记录,但是field2
的类型仍然是string option
,而不是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)
除非还有其他问题?