我最近一直在教自己F#,我来自命令式(C ++ / C#)背景。作为一个练习,我一直在研究可以用矩阵做东西的函数,比如添加,乘法,得到决定因素等等。在这方面一切都很顺利,但我发现在涉及处理时我可能没有做出最好的决定输入无效,例如:
// I want to multiply two matrices
let mult m1 m2 =
let sizeOK = validateDims m1 m2
// Here is where I am running to conceptual trouble:
// In a C# world, I would throw an exception.
if !sizeOK then
raise (InvalidOperationException("bad dimensions!")
else
doWork m1 m2
因此,虽然这在技术上有效,但这适用于功能语言吗?它是否符合函数式编程的精神?或者将它重写为:
更有意义let mult m1 m2 =
let sizeOK = validateDims m1 m2
if !sizeOK then
None
else
Some doWork m1 m2
在这种情况下,我返回一个选项,它在矩阵周围添加了一个额外的层,但我也可以使用函数的结果,即使在失败的情况下(无)与模式匹配等,稍后在该程序。那么这些类型的场景是否有最佳实践?功能程序员会做什么?
答案 0 :(得分:10)
我倾向于避免例外,原因如下:
在您的情况下,我将遵循F#核心库惯例(例如List.tryFind
和List.find
等)并创建两个版本:
let tryMult m1 m2 =
let sizeOK = validateDims m1 m2
if not sizeOK then
None
else
Some <| doWork m1 m2
let mult m1 m2 =
let sizeOK = validateDims m1 m2
if not sizeOK then
raise <| InvalidOperationException("bad dimensions!")
else
doWork m1 m2
此示例例外不足以使用异常。包含mult
函数用于C#兼容性。在C#中使用库的人没有模式匹配来轻松分解选项。
选项的一个缺点是它们没有说明函数没有产生值的原因。这里太过分了;通常Choice(或Haskell术语中的monad)更适合error handling:
let tryMult m1 m2 =
// Assume that you need to validate input
if not (validateInput m1) || not (validateInput m2) then
Choice2Of2 <| ArgumentException("bad argument!")
elif not <| validateDims m1 m2 then
Choice2Of2 <| InvalidOperationException("bad dimensions!")
else
Choice1Of2 <| doWork m1 m2
很可惜,F#Core缺乏操纵Choice的高阶函数。您可以在FSharpX或ExtCore库中找到这些功能。
答案 1 :(得分:4)
我倾向于遵循以下准则:
在意外出错的函数中使用异常时应该始终具有返回值。这可以是例如如果参数不遵守函数的合同。这样做的好处是客户端代码变得更简单。
当函数有时具有有效输入的返回值时,请使用选项。这可以是例如得到有效密钥可能不存在的地图。因此,您强制用户检查该函数是否具有返回值。这可能会减少错误,但总是会混淆客户端代码。
你的情况介于两者之间。如果您希望它主要用于尺寸有效的地方,我会抛出异常。 如果您希望客户端代码经常使用无效维度调用它,我将返回一个选项。我可能会选择前者,因为它更清洁(见下文),但我不知道你的背景:
// With exception
let mult3 a b c =
mult (mult a b) c;
// With option
let mult3 a b c=
let option = mult a b
match option with
| Some(x) -> mult x b
| None -> None
免责声明:我没有函数式编程的专业经验,但我是研究生级F#编程的助教。
答案 2 :(得分:4)
我喜欢上述答案,但我想添加其他选项。这实际上取决于结果出乎意料的程度以及是否有意义继续进行。如果这是一个罕见的事件并且调用者可能不打算失败,那么异常是完全可敬的。捕获异常的代码可能是上面的许多级别,调用者可能不打算失败。如果操作失败是一个非常常规的结果,那么Some / None是可以的,虽然它只给你两个选项而无法传递结果。另一种选择是将可能性区分开来。这会强制调用者可能在不同的结果上匹配,是可扩展的,并且不会强制您使每个结果都具有相同的数据类型。
e.g。
type MultOutcome =
| RESULT of Matrix
| DIMERROR
| FOOERROR of string
let mult a b =
if dimensionsWrong then
DIMERROR
elif somethingElseIDoNotLike then
FOOERROR("specific message")
else
DIMRESULT(a*b)
match mult x y with
| DIMERROR -> printfn "I guess I screwed up my matricies"
| FOOERROR(s) -> printfn "Operation failed with message %s" s
| DIMRESULT(r) ->
// Proceed with result r