Comments (18)
I was able to design an async
version like this:
[<AutoOpen>]
module AsyncSeqExtensions =
// Add asynchronous for loop to the 'asyncResult' computation builder
type FsToolkit.ErrorHandling.AsyncResultCE.AsyncResultBuilder
with
member x.For (seq : AsyncSeq<Result<'T, 'TError>>, action : 'T -> Async<Result<unit, 'TError>>) : Async<Result<unit, 'TError>> =
seq
|> AsyncSeq.foldAsync
(fun acc x ->
async {
match acc, x with
| Ok (), Ok x ->
return! action x
| Ok (), Error e ->
return Error e
| acc, _ ->
return acc
})
(Ok ())
I'm still unsure of the best semantics though...
- Should the
for
stop at the firstResult.Error
? - Should only
AsyncSeq<Result<_, _>>
be supported?
from fstoolkit.errorhandling.
Looking at that code base it, AsyncSeq
isn't an extension of Async
it is its own type.
And to support the use case of iterating over them in an async
CE, they've added an extension member to the AsyncBuilder
.
So to add similar functionality for your case base you can add something similar.
[<AutoOpen>]
module AsyncSeqExtensions =
// Add asynchronous for loop to the 'asyncResult' computation builder
type FsToolkit.ErrorHandling.AsyncResultCE.AsyncResultBuilder with
member x.For (seq:AsyncSeq<'T>, action:'T -> Async<Result<unit, 'TError>>) =
seq |> AsyncSeq.iterAsync action
from fstoolkit.errorhandling.
Looks like that code I posted wont work. You'll have to implement your own traverse function to get the types to match up. You can take a look at examples from the List.fs to implement it.
from fstoolkit.errorhandling.
if you're looking for a super hacky solution
[<AutoOpen>]
module AsyncSeqExtensions =
// Add asynchronous for loop to the 'asyncResult' computation builder
type FsToolkit.ErrorHandling.AsyncResultCE.AsyncResultBuilder with
member x.For (seq:AsyncSeq<'T>, action:'T -> Async<Result<unit, 'TError>>) =
seq
|> AsyncSeq.mapAsync action
|> AsyncSeq.toListAsync //No longer evaluates as lazy
|> Async.map(FsToolkit.ErrorHandling.List.sequenceResultM)
|> AsyncResult.map ignore // removes `list<unit>` from success case
works but it doesn't support the lazy nature of seq.
from fstoolkit.errorhandling.
I didn't realize you could extend an existing computation expression in that way, very useful trick!
However, where should this extension live? I presume you don't want to take FSharp.Control.AsyncSeq
as a dependency?
from fstoolkit.errorhandling.
I’d say try it out in your code for a while before putting it in this library. When we go to do that we would end up making a new project like we did with Tasks or Hopac support.
from fstoolkit.errorhandling.
Should the for stop at the first Result.Error?
Yes this is how all others are implemented currently.
Should only AsyncSeq<Result<_, _>> be supported?
I'm unsure what you're asking.
from fstoolkit.errorhandling.
Ok so testing this solution also has an issue, it attempt to run through the whole AsyncSeq.
let myRunner2 : Async<Result<unit,string>>=
asyncResult {
let xs = asyncSeq {
printfn "yield 1"
yield 1
printfn "yield 2"
yield 2
printfn "yield 3"
yield 3
}
for x in xs do
if x % 2 = 0 then
do! Error "Evens are evil"
}
[<EntryPoint>]
let main argv =
let result = myRunner2 |> Async.RunSynchronously |> printfn "Result : %A"
0 // return an integer exit code
Will result in:
yield 1
yield 2
yield 3
Result : Error "Evens are evil"
So I dabbled and came up with a solution that fits that will stop execution if it no longer needs to
member this.While
(guard: unit -> Async<option<'T>>, computation: 'T -> Async<Result<unit, 'TError>>)
: Async<Result<unit, 'TError>> =
async {
match! guard () with
| Some x ->
return! this.Bind(computation x, fun () -> this.While(guard,computation))
| None ->
return! this.Zero()
}
member this.For
(sequence: AsyncSeq<'T>, binder: 'T -> Async<Result<unit, 'TError>>)
: Async<Result<unit, 'TError>> =
this.Using(sequence.GetEnumerator (), fun enum ->
this.While(enum.MoveNext, binder))
which the above code will now execute as:
yield 1
yield 2
Result : Error "Evens are evil"
from fstoolkit.errorhandling.
Should only AsyncSeq<Result<_, _>> be supported?
I'm unsure what you're asking.
Basically it comes to down to this example. Should it work?
let xs = asyncSeq {
yield 1
yield 2
yield 3
}
let ys = asyncSeq {
yield Ok 1
yield Ok 2
yield Ok 3
}
for x in xs do
printfn "%i" x
for y in ys do
printfn "%i" y // y is an int because the `Ok` is automatically unwrapped
from fstoolkit.errorhandling.
Ahh yeah it won't work with the sample I've given. This will work but with one caveat:
[<AutoOpen>]
module AsyncSeqExtensions =
type FsToolkit.ErrorHandling.AsyncResultCE.AsyncResultBuilder with
member this.While
(guard: unit -> Async<option<Result<'T,'TError>>>, computation: 'T -> Async<Result<unit, 'TError>>)
: Async<Result<unit, 'TError>> =
async {
match! guard () with
| Some (Ok x) ->
return! this.Bind(computation x, fun () -> this.While(guard,computation))
| Some (Error e) ->
return Error e
| None ->
return! this.Zero()
}
member this.For
(sequence: AsyncSeq<Result<'T,'TError>>, binder: 'T -> Async<Result<unit, 'TError>>)
: Async<Result<unit, 'TError>> =
this.Using(sequence.GetEnumerator (), fun enum ->
this.While(enum.MoveNext, binder))
member this.For
(sequence: AsyncSeq<'T>, binder: 'T -> Async<Result<unit, 'TError>>)
: Async<Result<unit, 'TError>> =
this.Using(sequence.GetEnumerator (), fun enum ->
let moveNext = enum.MoveNext >> Async.map(Option.map Ok)
this.While(moveNext, binder))
let xs = asyncSeq {
yield 1
yield 2
yield 3
}
let ys = asyncSeq {
yield Ok 1
yield Ok 2
yield Ok 3
}
for x in xs do
printfn "%A" x
for (y : int) in ys do
printfn "%A" y
The caveat is you have you add the type annotation in the ys
case to get the correct overload to be used to unwrap the result for you.
from fstoolkit.errorhandling.
Exactly!
I'm not sure which behaviour is preferable.
from fstoolkit.errorhandling.
I think having support for both is necessary even if the user has to use a type annotation.
from fstoolkit.errorhandling.
Ok, I think I've answered this. Going to close.
from fstoolkit.errorhandling.
In case anyone stumbles on this, I recently got around to packing this code: https://github.com/njlr/FsToolkit.ErrorHandling.AsyncSeq
#r "nuget: FSharp.Control.AsyncSeq"
#r "nuget: FsToolkit.ErrorHandling"
#r "nuget: FsToolkit.ErrorHandling.AsyncSeq"
open FSharp.Control
open FsToolkit.ErrorHandling
let xs =
asyncSeq {
1
2
3
}
asyncResult {
let mutable sum = 0
for x in xs do
sum <- sum + x
return sum
}
|> Async.RunSynchronously
|> printfn "%A"
Hope it helps!
from fstoolkit.errorhandling.
Nice! If you want to submit that for me to maintain in this repository as a PR, I'll be happy to do it. We'll have to figure out the nuget permissions tho.
from fstoolkit.errorhandling.
Sure, happy to.
Would it be one repo (this one) with multiple Nuget packages?
I can delete my Nuget package and give you the name if that's easier.
from fstoolkit.errorhandling.
Would it be one repo (this one) with multiple Nuget packages?
Yeah, we can just add it to this repo so it doesn't fall out of sync with any changes made here. And I'll just publish it to as the pacakge name you have.
I can delete my Nuget package and give you the name if that's easier.
Yeah if that's the simplest solution, let's do it. If not we'll figure it out :)
from fstoolkit.errorhandling.
Basics here: #138
from fstoolkit.errorhandling.
Related Issues (20)
- FSharp.Core version warning (net7.0) HOT 14
- Make Result.defaultWith accept error, or remove it HOT 2
- Overload issues with TaskResult on generic functions HOT 2
- Add Option.ofPair HOT 5
- Add asyncValidation CE, or make asyncResult (and result?) CEs work with 'and!' HOT 4
- Can we reinstate `TaskResult.foldResult` and the like? HOT 4
- Add Light/Dark Theme Toggle to Gitbook Docs
- Support ValueOption in FsToolkit.Errorhandling.asyncOption HOT 3
- Support TaskValidation to match AsyncValidation HOT 2
- Binding Async<'T option * 'T option> to AsyncValidation is having overload errors HOT 1
- Common Language Runtime detected an invalid program HOT 2
- TaskOption/TaskResult defaultValue and defaultWith
- Support requireValueSome and requireValueNone in Result, TaskResult and AsyncResult
- Async/Job/Task wrapped result + require functions helpers
- feat(Seq): Add sequenceResultA HOT 6
- Readme video unavailable HOT 1
- usage with npgsql HOT 7
- TaskResultOption compatibility with TaskResult and friends HOT 4
- TaskResult `return! Error ()` results in a compiler error: "Type constraint mismatch". The type 'TaskResultCode<,'b,> is not compatible with type 'TaskResultCode<unit,string,'a>' HOT 2
- Consider using other name for Option.filter HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fstoolkit.errorhandling.