suave icon indicating copy to clipboard operation
suave copied to clipboard

Include Bind for 'a option and 'a Async on AsyncOptionBuilder

Open iskandersierra opened this issue 8 years ago • 6 comments

This couple of functions have proved to be very useful on my projects but I have to copy them arround. Could they be included in the codebase? I could make a PR but that seems too much.

module Suave =
    module WebPart =
        type AsyncOptionBuilder with
            member this.Bind(x :'a option, f : 'a -> Async<'b option>) : Async<'b option> = bind f (Async.result x)
            member this.Bind(x :Async<'a>, f : 'a -> Async<'b option>) : Async<'b option> = bind f (Async.map Some x)

The idea is to be able to let! some Option<'a> and Async<'a> expressions to be bound inside asyncOption.

iskandersierra avatar Apr 05 '17 10:04 iskandersierra

Hi @iskandersierra – I can guess, but could you provide a sample using them, to see how you envision their usage?

haf avatar Apr 05 '17 12:04 haf

Hi @haf

The idea is to be able to do something like the following:

let app c = WebPart.asyncOption {
    let! r1 = anotherAsyncOption
    let! r2 = aValidationReturningOption r1
    let! r3 = anAsyncJobWithStringResult r2
    return! OK r3 c
}

The thing is to seamlessly use other APIs involving Option<'a> and Async<'a> inside a asyncOption expression.

iskandersierra avatar Apr 06 '17 11:04 iskandersierra

I like it. :+1:

ademar avatar Apr 06 '17 23:04 ademar

Hi @iskandersierra

I'd be up for it too, but how would you say it interacts with other types, like the new Result type or Choice2Of2? Shouldn't we have those as well?

haf avatar Apr 07 '17 07:04 haf

I don't think so. I would only include Option and Async because asyncOption is based on those types. For the rest there should be conversion functios to and from Async/Option

El vie., 7 de abril de 2017 9:36, Henrik Feldt [email protected] escribió:

Hi @iskandersierra https://github.com/iskandersierra

I'd be up for it too, but how would you say it interacts with other types, like the new Result type or Choice2Of2? Shouldn't we have those as well?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/SuaveIO/suave/issues/600#issuecomment-292464263, or mute the thread https://github.com/notifications/unsubscribe-auth/ADUGrRv8x8zjljP4yxLqnMXznfA5KsEeks5rtedggaJpZM4M0EOC .

iskandersierra avatar Apr 07 '17 07:04 iskandersierra

Here's another alternative that uses Choice, which I think is better until we release Suave v3 and have the Result type throughout:


  module WebPart =
    let internal bind f value =
      async {
        let! value = value
        match value with
        | Choice1Of2 res ->
          return! f res
        | Choice2Of2 err ->
          return Choice2Of2 err
      }

    let internal bindSnd f value =
      async {
        let! value = value
        match value with
        | Choice1Of2 res ->
          return Choice1Of2 res
        | Choice2Of2 err ->
          return! f err
      }

    let internal choose falts (value: Async<Choice<'res, 'err list>>): Async<Choice<'res, 'err list>> =
      let rec inner (acc: 'err list) = function
        | [] ->
          acc
          |> List.rev
          |> Choice.createSnd
          |> Async.result
        | a :: rest ->
          async {
            let! a = a
            match a with
            | Choice1Of2 value ->
              return Choice1Of2 value
            | Choice2Of2 err ->
              return! inner (err :: acc) rest
          }

      value |> Async.bind (function
        | Choice1Of2 x ->
          (falts >> List.ofSeq >> inner []) x
        | Choice2Of2 err ->
          Async.result (Choice2Of2 err))

    type AsyncChoiceBuilder() =
      member x.Return (value: 'a): Async<Choice<'a, 'err>> =
        async.Return (Choice.create value)

      member x.Zero (): Async<Choice<unit, 'err>> =
        x.Return ()

      member x.ReturnFrom (value: Async<Choice<'a, 'err>>) =
        value

      /// E.g. `asyncChoice { if true then printfn "hi"; return! ... }`
      /// See https://fsharpforfunandprofit.com/posts/computation-expressions-builder-part2/
      member x.Combine (value: Async<Choice<unit, 'err>>, f: unit -> Async<Choice<'a, 'err>>): Async<Choice<'a, 'err>> =
        async {
          let! value = value
          match value with
          | Choice1Of2 () ->
            return! f ()
          | Choice2Of2 err ->
            return Choice2Of2 err
        }

      member x.Delay(f: unit ->  Async<Choice<'a, 'err>>) =
        async { return! f () }

      /// Bind a monadic value to f
      member x.Bind (value: Async<Choice<'a, 'err>>,
                     f: 'a -> Async<Choice<'b, 'err>>): Async<Choice<'b, 'err>> =
        bind f value

      /// Bind plain choices to `f`, an async choice fn
      member x.Bind (value: Choice<'a, 'err>, f: 'a -> Async<Choice<'b, 'err>>): Async<Choice<'b, 'err>> =
        match value with
        | Choice1Of2 value ->
          f value
        | Choice2Of2 err ->
          async.Return (Choice2Of2 err)

      /// Bind a plan async to `f`, an async-choice fn.
      member x.Bind (value: Async<'a>, f: 'a -> Async<Choice<'b, 'err>>): Async<Choice<'b, 'err>> =
        async {
          let! value = value
          return! f value
        }

      member x.Bind (value: Hopac.Job<Choice<'a, 'err>>, f: 'a -> Async<Choice<'b, 'err>>): Async<Choice<'b, 'err>> =
        Hopac.Job.toAsync value |> bind f

      /// https://stackoverflow.com/questions/9272285/f-is-there-a-way-to-extend-the-monad-keyword-list/9275289#9275289
      /// https://mnajder.blogspot.se/2011/09/when-reactive-framework-meets-f-30.html
      [<CustomOperation("choose")>]
      member x.Choose (value: Async<Choice<'a, 'err list>>,
                       [<ProjectionParameter>] alts: 'a -> #seq<Async<Choice<'a, 'err>>>): Async<Choice<'a, 'err list>> =
        choose alts value

    let asyncChoice = AsyncChoiceBuilder()

We can conditionally compile the Hopac calls of course. I've also included a sample choose function, that monadically validates the given choices.

haf avatar Jun 25 '17 13:06 haf