Include Bind for 'a option and 'a Async on AsyncOptionBuilder
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.
Hi @iskandersierra – I can guess, but could you provide a sample using them, to see how you envision their usage?
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.
I like it. :+1:
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?
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 .
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.