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

A.R*_*.R. 13 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  
Run Code Online (Sandbox Code Playgroud)

因此,虽然这在技术上有效,但这适用于功能语言吗?它是否符合函数式编程的精神?或者将它重写为更有意义:

let mult m1 m2 =
  let sizeOK = validateDims m1 m2

  if !sizeOK then
    None
  else
    Some doWork m1 m2  
Run Code Online (Sandbox Code Playgroud)

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

pad*_*pad 10

我倾向于避免异常,原因如下:

  • .NET异常很慢
  • 例外以一种意想不到的方式改变程序的控制流程,这使得它更难以推理
  • 在危急情况下经常出现异常,而您可以通过使用选项进行故障安全.

根据你的情况,我会按照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 
Run Code Online (Sandbox Code Playgroud)

这个例子是没有例外就够用了异常.该mult功能包含在C#兼容性中.在C#中使用库的人没有模式匹配来轻松分解选项.

选项的一个缺点是它们没有给出函数没有产生值的原因.这里太过分了; 通常选择(或Haskell术语中的monad)更适合错误处理:

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
Run Code Online (Sandbox Code Playgroud)

遗憾的是F#Core缺乏操纵Choice的高阶函数.您可以在FSharpXExtCore库中找到这些功能.


Dar*_*ren 5

我喜欢上面的答案,但我想添加另一个选项。这实际上取决于结果的出乎意料程度以及继续进行是否有意义。如果这是一个罕见的事件,并且调用者可能没有计划失败,那么例外是完全值得尊重的。捕获异常的代码可能比上面许多级别,并且调用者可能不打算失败。如果操作失败确实是例行结果,则“Some/None”也可以,尽管它只提供两个选项并且无法传递结果。另一种选择是建立一个有区别的可能性联合。这迫使调用者可能匹配不同的结果,是可扩展的,并且不会强迫您使每个结果具有相同的数据类型。

例如

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
Run Code Online (Sandbox Code Playgroud)