GithubHelp home page GithubHelp logo

Comments (18)

njlr avatar njlr commented on July 27, 2024 1

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 first Result.Error?
  • Should only AsyncSeq<Result<_, _>> be supported?

from fstoolkit.errorhandling.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

njlr avatar njlr commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

njlr avatar njlr commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

njlr avatar njlr commented on July 27, 2024

Exactly!

I'm not sure which behaviour is preferable.

from fstoolkit.errorhandling.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

I think having support for both is necessary even if the user has to use a type annotation.

from fstoolkit.errorhandling.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

Ok, I think I've answered this. Going to close.

from fstoolkit.errorhandling.

njlr avatar njlr commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

njlr avatar njlr commented on July 27, 2024

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.

TheAngryByrd avatar TheAngryByrd commented on July 27, 2024

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.

njlr avatar njlr commented on July 27, 2024

Basics here: #138

from fstoolkit.errorhandling.

Related Issues (20)

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.