All Projects → cmeeren → Cvdm.errorhandling

cmeeren / Cvdm.errorhandling

Licence: mit
[DEPRECATED, use FsToolkit.ErrorHandling] AsyncResult and Result computation expressions and helper functions for error handling in F#

Programming Languages

fsharp
127 projects

Projects that are alternatives of or similar to Cvdm.errorhandling

Await Of
await wrapper for easier errors handling without try-catch
Stars: ✭ 240 (+410.64%)
Mutual labels:  async, error-handling
Preact Cli Plugin Async
Preact CLI plugin that adds converts async/await to Promises.
Stars: ✭ 44 (-6.38%)
Mutual labels:  async
Spring Boot
spring-boot 项目实践总结
Stars: ✭ 989 (+2004.26%)
Mutual labels:  async
Rq
Simple job queues for Python
Stars: ✭ 8,065 (+17059.57%)
Mutual labels:  async
Breeze
Javascript async flow control manager
Stars: ✭ 38 (-19.15%)
Mutual labels:  async
Bugsnag Android Ndk
DEPRECATED - this project now lives at bugsnag/bugsnag-android
Stars: ✭ 42 (-10.64%)
Mutual labels:  error-handling
Happy
the alchemist's happy path with elixir
Stars: ✭ 37 (-21.28%)
Mutual labels:  error-handling
Create React Redux App
This project was bootstrapped with Create React App and Redux, Sass Structure.
Stars: ✭ 46 (-2.13%)
Mutual labels:  async
Node Qiniu Sdk
七牛云SDK,使用 ES2017 async functions 来操作七牛云,接口名称与官方接口对应,轻松上手,文档齐全
Stars: ✭ 44 (-6.38%)
Mutual labels:  async
Fastapi
FastAPI Tutorials & Deployment Methods to Cloud and on prem infrastructures
Stars: ✭ 41 (-12.77%)
Mutual labels:  async
Rollbar Android
Rollbar for Android
Stars: ✭ 41 (-12.77%)
Mutual labels:  error-handling
Vue Async Computed
Async computed properties for Vue.js
Stars: ✭ 990 (+2006.38%)
Mutual labels:  async
Scout
Friendly fuzzy finder made in rust
Stars: ✭ 43 (-8.51%)
Mutual labels:  async
React Shimmer
🌠 Async loading, performant Image component for React.js
Stars: ✭ 990 (+2006.38%)
Mutual labels:  async
Caf
Cancelable Async Flows (CAF)
Stars: ✭ 1,027 (+2085.11%)
Mutual labels:  async
Vim Strand
A barebones Vim plugin manger written in Rust
Stars: ✭ 38 (-19.15%)
Mutual labels:  async
Friendly Exception
An interface for an exception to be friendly
Stars: ✭ 41 (-12.77%)
Mutual labels:  error-handling
Uvloop
Ultra fast asyncio event loop.
Stars: ✭ 8,246 (+17444.68%)
Mutual labels:  async
Progress Activity
Easily add loading, empty and error states in your app.
Stars: ✭ 1,039 (+2110.64%)
Mutual labels:  error-handling
Vim Grepper
👾 Helps you win at grep.
Stars: ✭ 1,030 (+2091.49%)
Mutual labels:  async

DEPRECATED, REPLACED BY FsToolkit.ErrorHandling

This library has been merged into FsToolkit.ErrorHandling and is now deprecated. Original readme follows below.

Cvdm.ErrorHandling

NuGet Build status

asyncResult and result computation expressions and helper functions for error handling in F#.

Summary

  • Use result { } for monadic error handling
  • Use asyncResult { } instead of result { } if you need to call functions returning Async<_>
  • The library is auto-opened, so you don’t need to open Cvdm.ErrorHandling (except when using Fable)
  • The library includes several helper functions in the Result and AsyncResult modules, see Helpers.fs for signatures and details

The result computation expression

The result computation expression simplifies synchronous error handling using F#'s Result<_,_> type. A single computation expression must have a single error type, and helper functions simplify transforming return values to a Result with the needed error type. Here's an example:

// Given the following functions:
//   tryGetUser: string -> User option
//   isPwdValid: string -> User -> bool
//   authorize: User -> Result<unit, AuthError>
//   createAuthToken: User -> AuthToken

// Here's how a simple login usecase can be written:

type LoginError = InvalidUser | InvalidPwd | Unauthorized of AuthError

let login (username: string) (password: string) : Result<AuthToken, LoginError> =
  result {
    // requireSome unwraps a Some value or gives the specified error if None
    let! user = username |> tryGetUser |> Result.requireSome InvalidUser

    // requireTrue gives the specified error if false
    do! user |> isPwdValid password |> Result.requireTrue InvalidPwd

    // Error value is wrapped/transformed (Unauthorized has signature AuthError -> LoginError)
    do! user |> authorize |> Result.mapError Unauthorized

    return user |> createAuthToken
  }

The expression above will stop at and return the first error.

The asyncResult computation expression

The asyncResult computation expression is more or less identical to the result expression except it's centered around Async<Result<_,_>>, with overloads supporting Result<_,_> (which is wrapped in Async) and Async<_> (whose value is wrapped using Result.Ok). Overloads also exists for Task<_> and Task. In other words, on the right side of let!, do! etc. you can have Async<Result<_,_>>, Async<_>, Result<_,_>, Task<Result<_,_>>, Task<_>, or Task.

asyncResult is intended to be almost a drop-in replacement of result. If you have a result expression and you need to unwrap Async values inside it, just change it to asyncResult. (The consumers of the changed expression will of course now need to change since it's Async<Result<_,_>> instead of Result<_,_>, but the contents of the expression itself should not need to change just from this switch.)

AsyncResult has more or less the same helper functions as Result. Here's the same example as above, with some signatures modified a bit:

// Given the following functions:
//   tryGetUser: string -> Async<User option>                 <- this is async now
//   isPwdValid: string -> User -> bool                       <- still synchronous
//   authorize: User -> Async<Result<unit, AuthError>>        <- this is async now
//   createAuthToken: User -> Result<AuthToken, TokenError>   <- still synchronous, but can now fail

type LoginError = InvalidUser | InvalidPwd | Unauthorized of AuthError | TokenErr of TokenError

let login (username: string) (password: string) : Async<Result<AuthToken, LoginError>> =
  asyncResult {
    // tryGetUser is async, so we use the function from the AsyncResult module
    let! user = username |> tryGetUser |> AsyncResult.requireSome InvalidUser

    // isPwdValid returns Result, so we still use the function from the `Result` module
    do! user |> isPwdValid password |> Result.requireTrue InvalidPwd

    // authorize is async, so again we use AsyncResult instead of Result
    do! user |> authorize |> AsyncResult.mapError Unauthorized

    // createAuthToken evaluates to a Result, but using return! (and other bang keywords)
    // it's automatically wrapped in Async to be compatible with the computation expression
    return! user |> createAuthToken |> Result.mapError TokenErr
  }

The helper functions

The library includes several helper functions in the Result and AsyncResult modules, see Helpers.fs for signatures and details.

A note on namespace imports

This library is tagged with the AutoOpen attribute. Referencing this library will automatically open the Cvdm.ErrorHandling namespace. This means you will have access to this library's computation expressions and helper methods in every file without any explicit open statements.

Should you have a value or function in your code with the same name as a value or function in this library (e.g. result/Result.defaultValue), your definition will take precedence. You may override this behaviour in a file and use the library's definition by explicitly opening Cvdm.ErrorHandling again.

A note on type inference

Due to limitations in the F# type inference system when overloads are involved (see Microsoft/visualfsharp#4472), you might sometimes have to add explicit type annotations. For example, the following expression

asyncResult {
  let! str = asyncResult { return "" }
  return str.Length
         ^^^^^^^^^^ <- error FS0072
}

will give you an error on str.Length saying

error FS0072: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.

The solution is to annotate str:

let! (str: string) = asyncResult { return "" }

Things seem to work fine when the right-hand side is Async<_> or Result<_,_>.

Using this library in a Fable project

This library can be used with Fable. In Fable projects, AutoOpen instructions as used by this library can fail. In this case, just add an explicit open statement (open Cvdm.ErrorHandling).

A technical note on overload resolution

(Aside from the type inference limiation mentioned above, it all "just works" as you'd want; this is for the curious.)

If you have an expression of type Async<Result<_,_>>, then the compiler normally doesn't know how to choose between the overloads taking Async<Result<_,_>> and Async<_> since both are compatible. I have solved this by having the Async<_> members as extension methods. The compiler will then give these lower priority. I consider this bit of "magic" completely acceptable in this situation since

  1. the resulting behavior is intuitive and exactly what you want,
  2. it still follows strict rules (as defined above), and
  3. the whole point of this computation expression is to make your life easier when handling asynchronous errors. The alternative is to explicitly wrap all Async<Result<_,_>> expressions in a wrapper type to make overload resolution work (like Chessie does) which IMHO doesn't really add any meaningful clarity.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].