demystifyfp / fstoolkit.errorhandling Goto Github PK
View Code? Open in Web Editor NEWAn opinionated F# Library for error handling
Home Page: https://demystifyfp.gitbook.io/fstoolkit-errorhandling
License: MIT License
An opinionated F# Library for error handling
Home Page: https://demystifyfp.gitbook.io/fstoolkit-errorhandling
License: MIT License
Doesn't it make sense to also add Option computation expression to this library?
Now that F# 6.0 has native task {} support we should figure out how/when to integrate this.
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 {}
.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 AutoOpen
ed. For the rationale behind this, see cmeeren/Cvdm.ErrorHandling#1.
Thoughts?
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#?
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?
It would be nice to be on par with Chessie, which also has warnings support, which are not necessarily errors.
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
}
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
{"documents":{"/Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/*":"https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/*"}}
$ sourcelink print-urls FsToolkit.ErrorHandling.pdb
108c97e71af54a0fb3d40544095ae9a5 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/List.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/List.fs
5de054cef9dc4df18b20eb43abcc6ab6 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Option.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/Option.fs
f29e4ec4cf209b62ab44e4e2aa55efbd md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ValidationOp.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/ValidationOp.fs
c90e64326f61e44e9dfee84b6828898f md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Validation.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/Validation.fs
3490909bf050939a5258f24bc0fad018 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOptionOp.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/AsyncResultOptionOp.fs
30a0c1016c5011441911eda7da118dce md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOptionCE.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/AsyncResultOptionCE.fs
840ae0ef844559a92b9a86c77456960f md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOption.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/AsyncResultOption.fs
36c29760b7f567f12cc226232f268c26 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultOp.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/AsyncResultOp.fs
a712621482aa9e2aa0323c7eee6ac978 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResultCE.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/AsyncResultCE.fs
42d00717f40ef5ddb62cc8ed10635bff md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResult.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/AsyncResult.fs
556bcf6f4166469c5a98a3ec6d39420a md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Async.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/Async.fs
aa3a4737f0c5e839cd1825b90326a705 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOptionOp.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/ResultOptionOp.fs
c963b912f42bc2969335b0510b08021f md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOptionCE.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/ResultOptionCE.fs
1c204f50344ff62a253ac5b1f960d1d0 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOption.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/ResultOption.fs
d42cef933bd340839363448e7ebbe9fe md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultOp.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/ResultOp.fs
c55928aefcfea39715a36e86352b2b3c md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/ResultCE.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/src/FsToolkit.ErrorHandling/ResultCE.fs
7b5add9b5220ce7f4f11f191e19fabd2 md5 fsharp /Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/Result.fs
https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/68a1c34d29809b5c60cb53ee9e7d4591229fb54f/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.
Since this library claims to support Fable, we should run tests against javascript to prove that it does.
https://github.com/Zaid-Ajaj/Fable.Mocha 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.
Do you intend to implement some of the functions Scott Wlaschin has in its AsyncResult/Result modules, e.g.
AsyncResult.catch
AsyncResult.ofAsync
AsyncResult.ofSuccess
AsyncResult.ofError
AsyncResult.ofResult?
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):
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/result/map2
Of course, not relevant to the very matter that is under discussion here, but: Examples of longitude and latitude are incorrect. Longitude is -180 .. 180 and Latitude is -90 .. 90.
For example https://demystifyfp.gitbook.io/fstoolkit-errorhandling/result/map2
Notice how every '
is interpreted as a start/end string delimiter, causing incorrect colors.
Creating a separate issue for this so it doesn't get lost. Previously mentioned in #3 (comment)
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
{"documents":{"/Users/tamizhvendan/DemystifyFpWorks/FsToolkit.ErrorHandling/*":"https://demystifyfp/raw/demystifyfp/FsToolkit.ErrorHandling/1c752129694c21842af13da041ec4def9a152327/*"}}
However when building locally against my own I seem to be a sane URL
sourcelink print-json FsToolkit.ErrorHandling.pdb
{"documents":{"/Users/jimmybyrd/Documents/GitHub/FsToolkit.ErrorHandling/*":"https://raw.githubusercontent.com/TheAngryByrd/FsToolkit.ErrorHandling/ac855aced4ee0a31119b3aa348021ae28321ac70/*"}}
I'm guessing dotnet/sourcelink#278 might be related.
So my remotes listed are using https.
git remote -v
origin https://github.com/TheAngryByrd/FsToolkit.ErrorHandling.git (fetch)
origin https://github.com/TheAngryByrd/FsToolkit.ErrorHandling.git (push)
upstream https://github.com/demystifyfp/FsToolkit.ErrorHandling.git (fetch)
upstream https://github.com/demystifyfp/FsToolkit.ErrorHandling.git (push)
@demystifyfp Do yours happen to be using git or are they https?
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).
@TheAngryByrd I just noticed that we don't have the documentation for the JobResult library. We need to add it when we find the time!
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 Result.map
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.
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.
Hi!
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:
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/asyncresult/others#witherror
'a -> Async<Result<'b, uni>t>
-> Async<Result<'b, 'a>> should probably be
'a -> Async<Result<'b, unit>> -> Async<Result<'b, 'a>>
Similarly,
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/jobresult/others#witherror
'a -> job<Result<'b, uni>t>
-> job<Result<'b, 'a>> should probably be
'a -> job<Result<'b, unit>> -> job<Result<'b, 'a>>
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/taskresult/map#example-1
TaskResult.map, example 1, code block 2, line 2
let rawPostId : Task<Result<Guid, exn>> =
savePost createostRequest
|> TaskResult.map (fun (PostId postId) -> postId)
should probably be
let rawPostId : Task<Result<Guid, exn>> =
savePost createPostRequest
|> TaskResult.map (fun (PostId postId) -> postId)
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/resultoption/map2#example-2
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
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/asyncresult/map2#example-1
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
The same code regarding UserId
appears in the following two places:
TaskResult.map2
: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/taskresult/map2#example-1JobResult.map2
: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/jobresult/map2#example-1https://demystifyfp.gitbook.io/fstoolkit-errorhandling/jobresult/foldresult#example-1
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.
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/taskresult/foldresult#example-1
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!
Stefan
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 |> AsyncX.map 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 |> AsyncX.map 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?
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
lng
// double -> Result<Longitude, string>
static member TryCreate (lng : double) =
if lng > -90. && lng < 90. then
Ok (Longitude lng)
else
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)
else
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) =
createPostRequest
<!^> 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? =/
So i've got this logic:
cacheGet
| Success -> return data
| Failure ->
apiGet
|> 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>>
apiGet()
|> TaskResult.bind (fun (a, b) ->
Caching.cacheData b |> Task.map (fun _ -> Ok a) // I want to store b, but return a, a is the deserialised form
)
Caching.hasCachedData
|> 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?
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.
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
}
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 =
function
| Ok v -> Some v
| Error _ -> None
Is this something worth adding to this project? (or are we doing it wrong)
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.
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?
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
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.
I notice those are implemented using recursion. According to Scot Wlaschin, its more efficient (tail-recursion) to implement those functions using fold.
See https://fsharpforfunandprofit.com/posts/elevated-world-4/#implementing-traverse-using-fold
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.
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 (Result.xxx
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 open
s 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.)
Does it make sense to use [< struct >] for computation expression builders?
[<Struct>]
type AsyncResultBuilder()
Currently it can be annoying to use asyncOption
because I have to append |> Async.map 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.)
I can submit a PR this weekend if any interest.
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.
Example:
asyncResult {
let! (r : Result<_,_>) = AsyncResult.retn innerData
Expect.equal r (Ok innerData) "Should be ok"
}
Will no longer compile.
Methods like ConnectAsync returns a non-generic Task, which cannot be used with AsyncResult.ofTask
Hello
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
someResult
|> Result.bind (
fun okValue ->
result{
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.
Thanks
Consider using MiniScaffold or Waypoint style Github Actions instead of Travis.
Why? Travis is extremely slow.
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.
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
inline
as many CE members as is practical, andI 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
The example on this page does not render properly: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/asyncresult/map2
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.")
trn.Commit()
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
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 AsyncResult.map (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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.