Skip to content

Instantly share code, notes, and snippets.

@panesofglass
Last active August 21, 2017 22:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panesofglass/f36ef58b62dfe65d3380183b276b13bc to your computer and use it in GitHub Desktop.
Save panesofglass/f36ef58b62dfe65d3380183b276b13bc to your computer and use it in GitHub Desktop.
F# async applicative using a custom operation
open System
type FSharp.Control.AsyncBuilder with
[<CustomOperation("and!", IsLikeZip=true)>]
member __.Merge(x, y, f) =
async {
let! token = Async.CancellationToken
let! x' = Async.StartChildAsTask x
let! y' = Async.StartChildAsTask y
do System.Threading.Tasks.Task.WaitAll([|x';y'|], cancellationToken = token)
let! x'' = Async.AwaitTask x'
let! y'' = Async.AwaitTask y'
return f x'' y''
}
member __.For(m, f) = __.Bind(m, f)
let a (sw:Diagnostics.Stopwatch) = async {
printfn "starting a %O" sw.ElapsedMilliseconds
do! Async.Sleep 1000
printfn "returning a %O" sw.ElapsedMilliseconds
return 1
}
let b (sw:Diagnostics.Stopwatch) = async {
printfn "starting b %O" sw.ElapsedMilliseconds
do! Async.Sleep 500
do printfn "returning b %O" sw.ElapsedMilliseconds
return 2
}
let comp sw = async {
for x in a sw do
``and!`` y in b sw
return x + y
}
let compBind sw = async {
let! x = a sw
let! y = b sw
return x + y
}
let sw = Diagnostics.Stopwatch.StartNew()
let result = comp sw |> Async.RunSynchronously
sw.Stop()
printfn "comp ran in %Oms with result %i" sw.ElapsedMilliseconds result
// Compare with
sw.Reset()
sw.Start()
let resultBind = compBind sw |> Async.RunSynchronously
sw.Stop()
printfn "compBind ran in %Oms with result %i" sw.ElapsedMilliseconds resultBind
let c (sw:Diagnostics.Stopwatch) = async {
printfn "starting c %O" sw.ElapsedMilliseconds
do! Async.Sleep 1500
do printfn "returning c %O" sw.ElapsedMilliseconds
return 3
}
let comp3 sw = async {
for x in a sw do
``and!`` y in b sw
``and!`` z in c sw
return x + y + z
}
sw.Reset()
sw.Start()
let result3 = comp3 sw |> Async.RunSynchronously
sw.Stop()
printfn "comp3 ran in %Oms with result %i" sw.ElapsedMilliseconds result3
@panesofglass
Copy link
Author

panesofglass commented Aug 19, 2017

Types of x and y in the first part of comp are obj, though the final yield x, y is typed as int * int. Also, defining a let binding in that computation causes the type inference to get messed up. Results were definitely run in parallel, though:
comp

Compare with compBind:
compBind

Results of running comp3 with multiple and! combinators:
comp3

@gusty
Copy link

gusty commented Aug 21, 2017

Nice, but I wonder if defining and! as 'run both operations in parallel' is the right abstraction for Async.
I mean, Async is async, not necessarily parallel.
What do you think?

@panesofglass
Copy link
Author

panesofglass commented Aug 21, 2017

@gusty, the general idea is, I think, something like what is available in FSharpx.Async. See this thread. There's no reason it must be parallel, but there's no reason it shouldn't be parallel either. It's beneficial that it is parallel for performance reasons.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment