结果与提高F#异步?

时间:2018-11-27 18:51:52

标签: f# async-await

async工作流程中似乎有两种返回错误的方法:raiseResult

let willFailRaise = async {
  return raise <| new Exception("oh no!")
}

let willFailResult = async {
  return Result.Error "oh no!"
}

对于呼叫者,处理方式有所不同:

async {
  try 
    let! x = willFailRaise
    // ...
  with error -> 
    System.Console.WriteLine(error)
}

async {
  let! maybeX = willFailResult
  match maybeX with
  | Result.Ok x -> 
    // ...
  | Result.Error error -> 
    System.Console.WriteLine(error)
}

我的问题是:

  • 每种方法的优点/缺点是什么?
  • 哪种方法更惯用F#?

3 个答案:

答案 0 :(得分:6)

这是F#编程的许多方面之一,在语言及其社区的核心中受到思维分裂的困扰。

一方面,您拥有“ F#.NET Framework语言”,其中异常是用于处理错误的机制,另一方面,您可以从世界的Haskell借用其惯用法“ F#函数式编程语言”。这就是Result(也称为Either)的来源。

“哪个是惯用的”这个问题的答案将根据您询问的人和他们所看到的内容而改变,但是我的经验告诉我,you're better off using exceptions如有疑问。 Result类型在适度中有其用途,但是结果密集型编程风格很容易失控,一旦发生这种情况就不太美观了。

答案 1 :(得分:5)

  

提高

优势

  • .NET互操作性更好,因为抛出异常在.NET中相当普遍
  • 可以创建自定义例外
  • 更容易获得堆栈跟踪,因为它就在那里
  • 在大多数标准的异步操作(例如从网页中读取)中,您可能始终必须处理库代码中的异常
  • 使用旧版本的F#

缺点:

  • 如果您不知道它可能引发异常,则可能不知道捕获该异常。这可能会导致运行时爆炸
  

结果

优势

  • 异步函数的任何调用者都必须处理该错误,因此应避免运行时爆炸
  • 可以使用railway oriented programming style,这可以使您的代码更加简洁

缺点

  • 仅在F#4.1或更高版本中可用
  • 非F#语言难以使用
  • 用于结果的API并不全面。
    • 只有功能bindmapmapError
    • 一些很好的功能:
      • bimap : ('TSuccess -> 'a) -> ('TError -> 'e) -> Result<'TSuccess,'TError> -> Result<'a, 'e>
      • fold : ('TSuccess -> 'T) -> ('TError -> 'T) -> Result<'TSuccess, 'TError> -> 'T
      • isOk

答案 2 :(得分:4)

这取决于我们在谈论哪种错误。基本上有三种:

  • 域错误(例如,用户提供的无效数据,使用此电子邮件的用户已注册等)
  • 基础结构错误(例如,您无法连接到另一个微服务或数据库)
  • 由程序员的错误引起的恐慌(例如NullReferenceExceptionStackOverflowException等)。

尽管两种方法都可以完成工作,但是通常您需要考虑的是使您的代码具有自记录性并易于阅读。这意味着:

  • 域错误:肯定适用于Result。这些“错误”是预期的,它们是您工作流程的一部分。使用Result可以在功能签名中反映您的业务规则,这非常有用。
  • 基础结构故障:。如果您有微服务,则可能是那些失败的预料,并且使用Result可能会更方便。如果不是,请寻求例外。
  • panics:肯定是Exception。首先,您无法用Result覆盖所有内容,无论哪种方式都需要全局异常过滤器。第二件事-如果您试图掩盖所有可能的紧急情况-代码很快就会变成令人讨厌的灾难,这将使使用Result来处理域错误的整个过程都丧命。

因此,实际上这与Async或C#互操作无关,它与代码的可读性和可维护性有关。 对于C#iterop -不用担心,Result具有所有帮助方法,例如IsError等。但您始终可以添加扩展方法:

[<AutoOpen>] module Utils = type Result<'Ok, 'Error> with member this.Value = match this with | Ok v -> v | Error e -> Exception(e.ToString()) |> raise