F#:有些,无,或异常?

时间:2013-08-27 15:33:19

标签: f# c#-to-f# f#-3.0

我最近一直在教自己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  

在这种情况下,我返回一个选项,它在矩阵周围添加了一个额外的层,但我也可以使用函数的结果,即使在失败的情况下(无)与模式匹配等,稍后在该程序。那么这些类型的场景是否有最佳实践?功能程序员会做什么?

3 个答案:

答案 0 :(得分:10)

我倾向于避免例外,原因如下:

  • .NET exceptions are slow
  • 异常以意想不到的方式改变程序的控制流程,这使得理由更难以理解
  • 在危急情况下经常出现异常,而您可以通过使用选项进行故障安全。

在您的情况下,我将遵循F#核心库惯例(例如List.tryFindList.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的高阶函数。您可以在FSharpXExtCore库中找到这些功能。

答案 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