Result vs raise in F# async?

情到浓时终转凉″ 提交于 2019-12-23 07:08:29

问题


It seems like there are two ways to return errors in an async workflow: raise and Result.

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

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

For the caller, the handling is a bit different:

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)
}

My questions are:

  • What are the advantages / disadvantages of each approach?
  • Which approach is more idiomatic F#?

回答1:


This is one of the many aspects of F# programming that suffers from the mind-split at the core of the language and its community.

On one hand you have "F# the .NET Framework language" where exceptions are the mechanism for handling errors, on the other - "F# the functional programming language" that borrows its idioms from the Haskell side of the world. This is where Result (also known as Either) comes from.

The answer to the question "which one is idiomatic" will change depending who you ask and what they have seen, but my experience has taught me that when in doubt, you're better off using exceptions. Result type has its uses in moderation, but result-heavy programming style easily gets out of hand, and once that happens it's not a pretty sight.




回答2:


It depends on what kind of error we are talking about. Basically there are three kinds:

  • Domain errors (e.g. user provided invalid data, user with this email is already registered, etc.)
  • Infrastructure errors (e.g you can't connect to another microservice or DB)
  • Panics (e.g. NullReferenceExceptionor StackOverflowException etc.), which are caused by programmers' mistakes.

While both approaches can get the job done, usually your concern should be to make your code as self-documented and easy-to-read as possible. Which means the following:

  • Domain errors: definitely go for Result. Those "errors" are expected, they are part of your workflow. Using Result reflects your business rules in function's signature, which is very useful.
  • Infrastructure failures: it depends. If you have microservices, then probably those failures are expected and maybe it would be more convenient to use Result. If not -- go for exceptions.
  • Panics: definitely Exception. First of all, you can't cover everything with Result, you gonna need global exception filter either way. Second thing -- if you try to cover all possible panics - code becomes a nasty disaster extremely fast, that will kill the whole point of using Result for domain errors.

So really this has nothing to do with Async or C# interop, it's about code readability and maintainability. As for C# iterop -- don't worry, Result has all the methods to help, like IsError and so on. But you can always add an extension method:

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




回答3:


Raise

Advantages

  • Better .NET interop as throwing exceptions is fairly common in .NET
  • Can create custom Exceptions
  • Easier to get the stack trace as it's right there
  • You probably have to deal with exceptions from library code anyways in most standard async operations such as reading from a webpage
  • Works with older versions of F#

Disadvantages:

  • If you aren't aware of it potentially throwing an exception, you might not know to catch the exception. This could result in runtime explosions

Result

Advantages

  • Any caller of the async function will have to deal with the error, so runtime explosions should be avoided
  • Can use railway oriented programming style, which can make your code quite clean

Disadvantages

  • Only available in F# 4.1 or later
  • Difficult for non-F# languages to use it
  • The API for Result is not comprehensive.
    • There are only the functions bind, map, and mapError
    • Some functions that would be nice to have:
      • bimap : ('TSuccess -> 'a) -> ('TError -> 'e) -> Result<'TSuccess,'TError> -> Result<'a, 'e>
      • fold : ('TSuccess -> 'T) -> ('TError -> 'T) -> Result<'TSuccess, 'TError> -> 'T
      • isOk


来源:https://stackoverflow.com/questions/53506325/result-vs-raise-in-f-async

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!