GithubHelp home page GithubHelp logo

demystifyfp / fstoolkit.errorhandling Goto Github PK

View Code? Open in Web Editor NEW
449.0 11.0 57.0 3.74 MB

An opinionated F# Library for error handling

Home Page:

License: MIT License

F# 99.92% Shell 0.02% Batchfile 0.01% Dockerfile 0.05%
fsharp error-handling railway-oriented-programming category-theory

fstoolkit.errorhandling's People


1eyewonder avatar akoslukacs avatar bartelink avatar cameronaavik avatar cmeeren avatar danyx23 avatar demystifyfp avatar dependabot[bot] avatar gdziadkiewicz avatar isaacabraham avatar jmaharman avatar kerams avatar larocceau avatar m-rinaldi avatar maxumka avatar meridaio avatar micha-kun avatar michaelwinch avatar ninofloris avatar njlr avatar no1melman avatar nojaf avatar ratsclub avatar saerosv avatar sep2 avatar sheridanchris avatar swoorup avatar tamizhvendan avatar theangrybyrd avatar ursenzler avatar


 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar


 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fstoolkit.errorhandling's Issues

Add Result.sequenceAsync

I propose adding Result.sequenceAsync (or is it Async.sequenceResult? I think it's the former). An example implementation is:

let sequenceAsync (resAsnc: Result<Async<'a>, 'b>) : Async<Result<'a, 'b>> =
    async {
      match resAsnc with
      | Ok asnc ->
          let! x = asnc
          return Ok x
      | Error err -> return Error err

I have had a need for this when using the Result.apply or operators (or the corresponding Validation operators, which I am using) with an Async-returning function. For example, I have the following code:

let! webhook =
  Workflows.createWebhook  // createWebhook is async
  <!> parseRequiredAttr a.callbackUrl WebhookCallbackUrl.TryCreate
  <*> parseOptOptAttr a.authorizationHeader WebhookAuthorizationHeader.TryCreate
  |> Result.sequenceAsync

The implementation details aren't important. The core concept is that I have an async function createWebhook that I apply to Result-wrapped arguments using <!> and <*>. Without Result.sequenceAsync, I end up with Result<Async<Webhook, Error>> whereas I'd like Async<Result<Webhook, Error>>.

This is a situation users will end up with every time they have an Async-returning function that they use with the apply/map operators. It seems general/common/simple enough that it's worth adding to the library.

compilation warning in Fable AsyncResult

Hello, thank you for a great library.

I'm not really sure if this issue should be created here, in Fable repository or it is something I did wrong.

After I installed FsToolkit.ErrorHandling to my Safe stack project I started getting compilation error from Fable compiler:

WARNING in ./.fable/FsToolkit.ErrorHandling.1.4.0/AsyncResult.fs 52:13-22
"export 'awaitTask' was not found in '../fable-library.2.10.2/Async.js'

Any help would be appreciated


Seeing there are methods for AsyncOption, TaskResult, and even TaskResultOption, I'm surprised that there isn't a module with functions for TaskOption. Even there is a computation expression, but no module functions. Exists any reason for that?

Add `Result.requireTrue` to take second parameter as a function

Currently Result.requireTrue takes second parameter as a value, would it make sense to add an overloaded version or new extension method to allow it to take a function. This can be used in following scenarios

let trn = con.BeginTransaction()
let! result = cmd.AsyncExecute() |> Result.requireEqualTo 1 (fun x -> trn.Rollback(); "Error executing.")

>=1.3.0 causes nonsensical Intellisense/compilation errors (due to applicatives?)

After upgrading from 1.2.6 to 1.3.0 or above, I get nonsensical compilation errors:


I guess this has to do with applicatives.

I am using <LangVersion>preview</LangVersion> to get access to nameof, which I use extensively in logging.

It builds fine with the VSBuild task on Azure Pipelines, but locally there's nothing that can make it compile except downgrading to 1.2.6.

Note that I am not even using and!; the error come just from upgrading FsToolkit.ErrorHandling.

The errors can often be "fixed" by reordering the bang operator lines if possible, but that's not always possible nor desirable. For example like the below (which can't be used due to incorrect behavior):


JobResult infixes compatible with Job infixes

Job infixes are very useful for concurrent development and would be ideal if JobResult had the same infix implementations. For example, <*> infix is used to pair jobs into a single job, sort of Task.WhenAll. From Hopac documentation, implemented infixes are:

module Infixes =
  // Query-Reply
  val ( *<+->= ): Ch<'q> ->   (Ch<'r> -> Promise<unit> -> #Job<'q>) -> Alt<'r>
  val ( *<+->- ): Ch<'q> ->   (Ch<'r> -> Promise<unit> ->      'q)  -> Alt<'r>
  val ( *<-=>= ): Ch<'q> -> (IVar<'r>                  -> #Job<'q>) -> Alt<'r>
  val ( *<-=>- ): Ch<'q> -> (IVar<'r>                  ->      'q)  -> Alt<'r>
  val ( *<+=>= ): Ch<'q> -> (IVar<'r>                  -> #Job<'q>) -> Alt<'r>
  val ( *<+=>- ): Ch<'q> -> (IVar<'r>                  ->      'q)  -> Alt<'r>
  // Message passing
  val ( *<-  ):      Ch<'x> -> 'x  -> Alt<unit>
  val ( *<+  ):      Ch<'x> -> 'x  -> Job<unit>
  val ( *<=  ):    IVar<'x> -> 'x  -> Job<unit>
  val ( *<=! ):    IVar<'x> -> exn -> Job<unit>
  val ( *<<= ):    MVar<'x> -> 'x  -> Job<unit>
  val ( *<<+ ): Mailbox<'x> -> 'x  -> Job<unit>
  // After actions
  val ( ^=>  ): Alt<'x> -> ('x -> #Job<'y>) -> Alt<'y>
  val ( ^->  ): Alt<'x> -> ('x ->      'y)  -> Alt<'y>
  val ( ^=>. ): Alt<_>  ->         Job<'y>  -> Alt<'y>
  val ( ^->. ): Alt<_>  ->             'y   -> Alt<'y>
  val ( ^->! ): Alt<_>  ->             exn  -> Alt<_>
  // Choices
  val ( <|>  ): Alt<'x> -> Alt<'x> ->     Alt<'x>
  val ( <|>* ): Alt<'x> -> Alt<'x> -> Promise<'x>
  val ( <~>  ): Alt<'x> -> Alt<'x> ->     Alt<'x>
  val ( <~>* ): Alt<'x> -> Alt<'x> -> Promise<'x>
  // Sequencing
  val ( >>=   ): Job<'x> -> ('x -> #Job<'y>) ->     Job<'y>
  val ( >>=*  ): Job<'x> -> ('x -> #Job<'y>) -> Promise<'y>
  val ( >>-   ): Job<'x> -> ('x ->      'y)  ->     Job<'y>
  val ( >>-*  ): Job<'x> -> ('x ->      'y)  -> Promise<'y>
  val ( >>=.  ): Job<_>  ->         Job<'y>  ->     Job<'y>
  val ( >>=*. ): Job<_>  ->         Job<'y>  -> Promise<'y>
  val ( >>-.  ): Job<_>  ->             'y   ->     Job<'y>
  val ( >>-*. ): Job<_>  ->             'y   -> Promise<'y>
  val ( >>-!  ): Job<_>  ->             exn  ->     Job<_>
  val ( >>-*! ): Job<_>  ->             exn  -> Promise<_>
  // Composition
  val ( >=>   ): ('x -> #Job<'y>) -> ('y -> #Job<'z>) -> 'x ->     Job<'z>
  val ( >=>*  ): ('x -> #Job<'y>) -> ('y -> #Job<'z>) -> 'x -> Promise<'z>
  val ( >->   ): ('x -> #Job<'y>) -> ('y ->      'z)  -> 'x ->     Job<'z>
  val ( >->*  ): ('x -> #Job<'y>) -> ('y ->      'z)  -> 'x -> Promise<'z>
  val ( >=>.  ): ('x -> #Job<_>)  ->         Job<'z>  -> 'x ->     Job<'z>
  val ( >=>*. ): ('x -> #Job<_>)  ->         Job<'z>  -> 'x -> Promise<'z>
  val ( >->.  ): ('x -> #Job<_>)  ->             'z   -> 'x ->     Job<'z>
  val ( >->*. ): ('x -> #Job<_>)  ->             'z   -> 'x -> Promise<'z>
  val ( >->!  ): ('x -> #Job<_>)  ->             exn  -> 'x ->     Job<_>
  val ( >->*! ): ('x -> #Job<_>)  ->             exn  -> 'x -> Promise<_>
  // Pairing
  val ( <&> ): Job<'x> -> Job<'y> -> Job<'x * 'y>
  val ( <*> ): Job<'x> -> Job<'y> -> Job<'x * 'y>
  val ( <+> ): Alt<'x> -> Alt<'y> -> Alt<'x * `'y>

I understand all (by now) are not implementable (there is no AltResult, nor ChResult, etc.) but the Job related infixes would be interesting and useful.

Enhance stack traces/debugging for CE's exposed in this project

I know that we don't typically throw exceptions when using results and such, but I think it would be a good idea to put the tips from dotnet/fsharp#4867 into place for the CEs in this repository. Mostly this comes down to

  • have CE's just call matching module-level functions where applicable, and
  • inline as many CE members as is practical, and
  • if try/with is used anywhere and there are guard clauses, be sure to have a catch-all branch that correctly throws the original exception.

Compatability with AsyncSeq?

So FSharp.Control.AsyncSeq extends async so that you can do this:

async {
  let xs = asyncSeq {
    yield 1
    yield 2 
    yield 3

  for x in xs do
    printfn "%A" x

But this is not implemented for asyncResult:

async {
  let xs = asyncSeq {
    yield 1
    yield 2 
    yield 3

  for x in xs do // Only seq allowed here
    printfn "%A" x

I would be great if this worked, although I'm not sure how it could be implemented. Perhaps this is just a limitation of F#?

AsyncResult.mapAsync? (or some other name)

I am beginning my journey with F# and I am just figuring out there is so much missing in F# standard library. That is a surprise for me, and I am so glad I have found this library full of useful stuff.

I am trying to port some of the functions I use currently in my other projects, for example:

let map??? (fn: 'T -> Async<'T2>) (input: AsyncResult<'T, 'TError>) : AsyncResult<'T2, 'TError> =
  async {
    match! input with
    | Ok t -> return! fn t |> Ok
    | Error e -> return Error e

let mapErr??? (fn: 'TError -> Async<'TError2>) (input: AsyncResult<'T, 'TError>) : AsyncResult<'T, 'TError2> =
  async {
    match! input with
    | Ok t -> return Ok t
    | Error e -> return! fn e |> Error

// P.S. elsewhere:
module AsyncX =
  let bind f x = async.Bind(x, f) // f: 'a -> Async<'b> -> x: Async<'a> -> Async<'b>
  let map f = bind (f >> async.Return) // f: 'a -> Async<'b> -> Async<'a> -> Async<'b>

I don't know what would be the best name for this map???, maybe mapAsync (original name in TypeScript version was mapP where P stands for Promise)โ€ฆ Or maybe it's more of a bind that a map family? It is so often that when you try to insert the async function into your pipeline, that async function is pipeline's Result<'a, 'b> agnostic, it returns just a -> Async<'b> and with this map??? I can easily insert it into the AsyncResult<'a, 'b> with no extra transformations.

What would you say?

Consider adding Result.requireNonNull

I really like this library! I just found myself wanting a function that takes a nullable value and makes sure that it is not null by turning it into an Error value if it is null (useful for the many dotnet function that might return null). Would you consider a PR that adds something like the code below?

let requireNotNull (errorValue : 'error) (value : 'a when 'a : null ) =
    match value with
    | null -> Error errorValue
    | nonnull -> Ok nonnull

Sourcelink http sources are wrong

After downloading the nuget package, unzipping it and running some of the sourcelink test commands it seems the URLs are incorrect.

$ sourcelink print-json FsToolkit.ErrorHandling.pdb
$ sourcelink print-urls FsToolkit.ErrorHandling.pdb
108c97e71af54a0fb3d40544095ae9a5 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/List.fs
5de054cef9dc4df18b20eb43abcc6ab6 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Option.fs
f29e4ec4cf209b62ab44e4e2aa55efbd md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ValidationOp.fs
c90e64326f61e44e9dfee84b6828898f md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Validation.fs
3490909bf050939a5258f24bc0fad018 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOptionOp.fs
30a0c1016c5011441911eda7da118dce md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOptionCE.fs
840ae0ef844559a92b9a86c77456960f md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOption.fs
36c29760b7f567f12cc226232f268c26 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOp.fs
a712621482aa9e2aa0323c7eee6ac978 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultCE.fs
42d00717f40ef5ddb62cc8ed10635bff md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResult.fs
556bcf6f4166469c5a98a3ec6d39420a md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Async.fs
aa3a4737f0c5e839cd1825b90326a705 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOptionOp.fs
c963b912f42bc2969335b0510b08021f md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOptionCE.fs
1c204f50344ff62a253ac5b1f960d1d0 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOption.fs
d42cef933bd340839363448e7ebbe9fe md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOp.fs
c55928aefcfea39715a36e86352b2b3c md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultCE.fs
7b5add9b5220ce7f4f11f191e19fabd2 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Result.fs

So additionally after trying to debug with the package, vscode cannot find the sources correctly because those Urls are not valid.

I'm thinking we are missing <PublishRepositoryUrl>true</PublishRepositoryUrl> in the fsproj. However when building locally I'm having issues testing the package.

Forking logic using Result.Error

So i've got this logic:

    | Success -> return data
    | Failure ->
        |> cacheSet

So if cacheGet is successful, then we just return the data.
If cacheGet fails, then we just go straight to the API, once the data comes back, if that is successful, cache it, otherwise filter the data through....

Obviouly this is a little trickier because everything is basically a return function ('a -> Task<Result<'b'c>>) and we're replacing the error branch with further successes....

I don't know if this problem is resonating with anyone, but to solve, I've created

module TaskResult =
   // inspired by the Result.valueOf
  let bindValueOr f res = // ('a -> Task<Result<'b,'c>>) -> Result<'b,'a> -> Task<Result<'b, 'c>>
    match res with
    | Ok x -> Task.singleton (Ok x)
    | Error x -> f x

  let bindTRValueOr f taskResult = // ('a -> Task<Result<'b,'c>>) -> Task<Result<'b,'a>> -> Task<Result<'b,'c>>
    taskResult |> Task.bind (bindValueOr f)

and usage:

  let apiGet = // () -> Task<Result<'a,'b>>
  let apiGetThenCache = // Task<Result<'a,'c>>
        |> TaskResult.bind (fun (a, b) -> 
            Caching.cacheData b  |> (fun _ -> Ok a) // I want to store b, but return a, a is the deserialised form

  |> Task.bindTRValueOr apiGetThenCache

I don't know if this is sparking anyone's thoughts - or if I've completely misused the suite of functions.... if there is a simpler way with existing

If there isn't - what would be the appropriate way/hunger to incorporate this into the current library?

JobResult issue with Error

Have a look at the following code, dummy works but dummyList does not. I am not sure why.

let dummy a =
    match a with
    | 5 -> Ok true
    | _ -> Error "Not 5"

let dummyList a =
    match a with
    | 5 -> Ok true
    | _ -> Error ["Not 5"]

let loginEx (userDBService : IUserDBService) (fileService : IFileService) username password =
    jobResult {
        let! a = dummy 5 // Works
        let! b = dummyList 5 // Errors

I use this module pretty much for any validation code I write

Currently, my work requires me to read in csvs, json files, and xmls and parse/validate the files. I use FsToolkit.ErrorHandling validation computation expression to validate group of attributes and collect any validation errors. FsToolkit.ErrorHandling makes this insanely easy to do and provides a clean, readable solution.

I just want to thank everybody who contributed to this library. It's really great, my life so much easier, and my code cleaner and easier to read/grok.

Some day, I'll buy you all beers/coffees/your beverages of choice. Thanks again.

Add Option/Maybe CE

Doesn't it make sense to also add Option computation expression to this library?

AsyncResut CE cannot bind Result<,> type since 1.4.1

Sadly since the version 1.4.1 the following code refuse to compile.

let getRepositorySettings apiKey (repository: Result<{| projectPermissions: ProjectPermissions; slug: string |}, AppError>) =
    asyncResult {
        let! repository = repository
        let! branches = BitbucketApi.branches apiKey repository.projectPermissions.project repository.slug
        let! prSettings = BitbucketApi.pullRequestSettings apiKey repository.projectPermissions.project repository.slug
        let! defaultReviewers = BitbucketApi.defaultReviewers apiKey repository.projectPermissions.project repository.slug
        let! masterRestrictions = BitbucketApi.masterBranchRestrictions apiKey repository.projectPermissions.project repository.slug
        let! permissionGroups = BitbucketApi.repositoryPermissionGroups apiKey repository.projectPermissions.project repository.slug

        return { project = repository.projectPermissions.project
                    repositorySlug = repository.slug
                    branches = branches
                    pullRequestSettings = prSettings
                    defaultReviewers = defaultReviewers
                    masterBranchRestrictions = masterRestrictions
                    permissionGroups = PermissionGroups.union repository.projectPermissions.permissionGroups permissionGroups }

The following line is the responsible
let! repository = repository

It looks like the Bind method accepting a Bind has been removed for some reason.

Gitbook: Some Typos


First of all I want to thank you for taking the time to write such a detailed documentation for every function in FsToolkint.Errorhandling!

While going through the text, I stumbled upon a few words that might be typos.

I'm just a beginner, so there might be mistakes on my part, but I decided to just list them here anyway:


'a -> Async<Result<'b, uni>t> -> Async<Result<'b, 'a>> should probably be
'a -> Async<Result<'b, unit>> -> Async<Result<'b, 'a>>



'a -> job<Result<'b, uni>t> -> job<Result<'b, 'a>> should probably be
'a -> job<Result<'b, unit>> -> job<Result<'b, 'a>>, example 1, code block 2, line 2

let rawPostId : Task<Result<Guid, exn>> =
savePost createostRequest
|> (fun (PostId postId) -> postId)

should probably be

let rawPostId : Task<Result<Guid, exn>> =
savePost createPostRequest
|> (fun (PostId postId) -> postId)

ResultOption.map2, example 2, second code box, line 16

// Result<Location option, string>
let locationR =
ResultOption.map2 location latR lngR

should probably be

// Result<Location option, string>
let locationR =
ResultOption.map2 location.Create latR lngR

location.Create was defined here: Result.map2, example 2


getFollowerIds : UserId -> Async<Result<UserId, exn>>

Based on the code that follows in this example, I think the function should return a UserId list:

getFollowerIds : UserId -> Async<Result<UserId list, exn>>

If this is correct, the comment above this piece of code should also mention UserId list, too:
Example 1, code box 3, line 2

// Async<Result<UserId, exn>>
let getFollowersResult = getFollowerIds req.UserId

should become:

// Async<Result<UserId list, exn>>
let getFollowersResult = getFollowerIds req.UserId

TaskResult.map2, JobResult.map2

The same code regarding UserId appears in the following two places:


JobResult.foldResult, example 1, line 14

// Job<Result<PostId, exn>>
let createPostAR = createPost httpReq

should probably mean

// Job<Result<PostId, exn>>
let createPostJR = createPost httpReq

Of course, this has no effect on how the code works. It's just that in the original example, AR was probably chosen to mean AsyncResult, so in the case of JobResult, adding JR might make sense.


The sample applies to the TaskResult example:
TaskResult.foldResult, example 1, line 14

// Task<Result<PostId, exn>>
let createPostAR = createPost httpReq

should probably be:

// Task<Result<PostId, exn>>
let createPostTR = createPost httpReq

Keep up the good work!


How to unwrap an async

I just upgraded from Cvdm.ErrorHandling to this.
In Cvdm.ErrorHandling, I could unwrap an Async<Result<User, string>> by using let! operator without piping into any of the AsyncResult or Result module conversion functions.

// tryGetUser = int -> Async<Result<User, string>>
let! myResult = tryGetUser(1) // returns a Result<User, string>

Now this fails because it's trying to look at the result type. But in this case, I don't want to fully unwrap it to be just a User -- I actually want it to be a Result<User, string> so that I can do some custom logic for the error case.

Is there a way to do this? I'm looking through the AsyncResult module, but I don't see anything relevant.

Enable asyncOption CE to bind Async<'a>

Currently it can be annoying to use asyncOption because I have to append |> Some everywhere I bind a computation that does not return Async<'a option> but just Async<'a>.

It would be great if the Bind and ReturnFrom members were overloaded to accept a plain Async<'a>. (These must be extension members to avoid overload resolution problems.)

Add Fable Tests

Since this library claims to support Fable, we should run tests against javascript to prove that it does. has an expecto like API that allows us to use compiler switch to easy switch between dotnet or javascript runtimes.

I've already started down this and will send a PR about this soon.

Making TaskResult and JobResult libraries

It was requested via twitter to have a TaskResult added to this library. I do believe this would be a good idea, as I also work with task heavy CE workloads and rely on the Result type.

As mentioned in that tweet thread, since the main library wants to be Fable compatible, it would have to be its own separate library. FsToolkit.ErrorHandling.Tasks (something something naming is hard).

In addition to this, I use Hopac quite a bit and have authored the Chessie.Hopac. Similar to the above, it would be its own library FsToolkit.ErrorHandling.Hopac.

I'd be willing to implement both of these libraries if this proposal makes sense.

try catch

Is there a way to specify an exception handler function for a given asyncResult block?
Currently I am manually putting a try catch inside the asyncResult, but it would be cool if I could specify a built-in try catch that would wrap maybe the bind (or run).

Native tasks

Now that F# 6.0 has native task {} support we should figure out how/when to integrate this.

  • I'm assuming this would force this library to make consumers go to F# 6.0.
  • Additionally, one of the reasons TaskResult library is separate is because it relied on upstream dependency and I always try to minimize the dependency tree that consumers need to use a library. The other is it makes it easier to deal with Fable instead of having large portions ifdef'd out. The first reason would no longer matter but the second still does unless Fable implements some shim for task {}.

Simplify namespace structure

While testing in #12 (comment), I noticed that I had to open separate namespaces to get the result and asyncResult instances, and yet another one (the main FsToolkit.ErrorHandling) to get the helpers ( etc.).

I suggest that we make result and asyncResult available in the main namespace (perhaps other CEs too?). While I may be biased to my own usage, I guess that people using this library are very likely to want these builders, so we shouldn't require them to have three opens in every relevant file.

(I haven't investigated the rest of the namespace/module structure of FsToolkit.ErrorHandling; there might be other places we could simplify, too.)

Use affine tasks instead of non-affine

Shouldn't the default behaviour be to use ply affine tasks instead of non-affine.

Reason being that current scheduler shouldn't be ignored. This allows having top-level control over degree of parallelism in which all child tasks would respect as well.

It's easy to override and use default scheduler even if using affine tasks than the other way around

Force binding on top wrapper

With the introduce of the Source members in #83, it's now not possible to tell the binding to handle only the Async<_> part of the Async<Result<_,_>> in a CE.


asyncResult {
    let! (r : Result<_,_>) = AsyncResult.retn innerData
    Expect.equal r (Ok innerData) "Should be ok"

Will no longer compile.

Consider adding ignore method to each type

I've found myself reaching many times for something like AsyncResult.ignore or AsyncResultOption.ignore to do the same thing that Async.Ignore does.

I've just been replacing it with (fun _ -> ()) in my code, but I thought it might be a useful addition to the library for when you want to use the do! syntax on a function and you don't care about the response.

Validation (Result.tryCreate)

Would it be possible to have a statically resolved parameter for a function instead of a type with a member?

Lets say I wanted to have utility function to try create something...

Instead of:

type Longitude = private Longitude of double with
  member this.Value =
    let (Longitude lng) = this

  // double -> Result<Longitude, string>
  static member TryCreate (lng : double) =
    if lng > -90. && lng < 90. then
      Ok (Longitude lng)
      sprintf "%A is a invalid longitude value" lng |> Error

something like this:

type Longitude = private Longitude of double

module Longitude =

  let value (Longitude x) = x

  // this could be named create / tryCreate / createResult
  let tryCreate (lng : dobule) =
     if lng > -90. && lng < 90. then
      Ok (Longitude lng)
      sprintf "%A is a invalid longitude value" lng |> Error

That way my validation would be something like this:

open FsToolkit.ErrorHandling.Operator.Validation 

// CreatePostRequestDto -> Result<CreatePostRequest, (string * string) list>
let validateCreatePostRequest (dto : CreatePostRequestDto) =
  <!^> Latitude.tryCreate "latitude" dto.Location.Latitude
  <*^> Longitude.tryCreate "longitude" dto.Location.Longitude
  <*^> Tweet.tryCreate "tweet" dto.Tweet

This would be another way of creating private type with value / tryCreate without defining those functions a member in the type. Does any of this make sense? =/

>=1.3.0 breaks type inference of unwrapped types

Thanks again for fixing #86. After updating to 1.4.1, however, I still get errors from code which compiled with 1.2.6.

Here's a minimal repro:

type X() = 
   member _.A = 1

let f () = asyncResult {
    let! v = async { return X() }
    return v.A

Under 1.2.6, this compiles. Under 1.4.1, the compiler can't figure out the type of v' and errors out with a "lookup of indeterminate object type" on v'.A

Note that if you replace X() with a record, the code may compile because F# will infer types from members of records, but not from members of classes and anonymous records.

Possible workaround: unwrap v inside a regular async CE instead:

let f () = asyncResult {
    let v = async { return X() }
    return! async { let! v' = v in return v'.A }

I'll still need to roll back to 1.2.6 though because we have this pattern every single time we make a database query:

asyncResult {
    use cmd = Db.Query(connStr)
    let! records = cmd.AsyncExecute(args)
    // do stuff with records

Still something wrong with SourceLink

After downloading the 1.1.1 nuget package, extracting and running sourcelink print-json, the URL is still incorrect.

 sourcelink print-json FsToolkit.ErrorHandling.TaskResult.pdb 

However when building locally against my own I seem to be a sane URL

sourcelink print-json FsToolkit.ErrorHandling.pdb

I'm guessing dotnet/sourcelink#278 might be related.

So my remotes listed are using https.

git remote -v
origin (fetch)
origin (push)
upstream (fetch)
upstream (push)

@demystifyfp Do yours happen to be using git or are they https?

Make FsToolkit.ErrorHandling a superset of Cvdm.ErrorHandling?

This library looks great! Good documentation and more comprehensive than Cvdm.ErrorHandling with regards to operators and choice of CEs (asyncResultOption).

Currently Cvdm.ErrorHandling has certain important features I don't immediately see in FsToolkit.ErrorHandling (having read the docs, not tested or browsed the code to any significant degree). I think I'd like for FsToolkit.ErrorHandling to be a superset of Cvdm.ErrorHandling and then simply retire the latter. The goals seem similar enough that I think this could be feasible, and it would benefit users by removing an unnecessary choice. How do you feel about that? Specifically:

  • Cvdm.ErrorHandling contains overloads on the asyncResult CE builder so that it can bind/return Result<_,_> expressions, not just Async<Result<_,_>>. This is a must for me as I use Result<_,_> and Async<Result<_,_>> expressions interchangeably all the time (in the same CEs), and I won't move to another error handling library that doesn't have this. Not sure how the overload trick will work with AsyncResultOption though, since it has three wrappers instead of two. Might work fine, might not work. Still, supporting this in AsyncResult is better than none.

  • Cvdm.ErrorHandling contains more members on the CE builders. Not sure from the top of my head what the practical differences are, but running relevant unit tests in Cvdm.ErrorHandling against FsToolkit.ErrorHandling should surface any missing features and incongruities in behaviour.

  • Cvdm.ErrorHandling contains quite a few helpers in the Result and AsyncResult modules (e.g. requireSome, requireTrue, requireEqual) to make it syntactically simpler to do ad-hoc validation in CEs when separate functions aren't really needed. See the readme for a couple of examples.

  • Cvdm.ErrorHandling is AutoOpened. For the rationale behind this, see cmeeren/Cvdm.ErrorHandling#1.


Async ce inside a Result ce


I am using 1.4.3
Is it possible to have an async ce inside a result ce ? I am trying to do the following

|> Result.bind (
   fun okValue ->
           let! (x: returnType) = async { return someAsynFunc okValue }
           return Ok (x)

and it gives me an error saying No overloads match for method source.
I have read through issue 84 and 88 but I am still unclear on the matter. Any help is appreciated.


Common Pattern Result -> Option

In many of our companies projects, we see this pattern. Where we need treat a result as an option.

The code normally looks something like this:

  module Option =
    let ofResult =
      | Ok v -> Some v
      | Error _ -> None

Is this something worth adding to this project? (or are we doing it wrong)

Source overload breaks `match!`

In #83 I started using the Source member to help with maintainability. However after using it in a work project, match! expressions became broken. @baronfel identified the issue in the F# compiler and have a PR open dotnet/fsharp#9407.

This is just a tracking issue for the upstream problem so others can find this easily.

Duplicate code?

I see that we have the operators in AsyncResultOp.fs file and also at the bottom of the AsyncResult.fs file... Is there a reason for that?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.