-
Notifications
You must be signed in to change notification settings - Fork 790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Async.Choice #807
Conversation
Hi @eiriktsarpalis, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution! TTYL, MSBOT; |
Awesome! What about the tests that I added? Do you think it would make sense to include these as well? |
@eiriktsarpalis, Thanks for signing the contribution license agreement so quickly! Actual humans will now validate the agreement and then evaluate the PR. |
@forki Yeah, I've seen them. The FsCheck test covers a superset of those. |
Cool. I closed my PR in favor of this one. |
match result with | ||
| Choice2Of2 edi -> args.aux.econt edi | ||
| Choice1Of2 [||] -> args.cont None | ||
| Choice1Of2 [|P body|] -> body args |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line reflects the original fssnip implementation: if given a singleton array as argument, simply pass your current continuation to it. This does not happen in Parallel
, so wondering whether it could violate expectations. @dsyme?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think just remove this case. It's better to run all operations consistently through the thread pool.
@forki what line was that? |
Thanks. I completely missed that. |
/// Async.Choice spec is satisfied | ||
let runChoice (ChoiceWorkflow(ops, cancelAfter)) = | ||
// Step 1. build a choice workflow from the abstract representation | ||
let completed = ref 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can use F# 4 i think, so let mutable completed
instead of ref
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK with refs being explicit if the author wishes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dsyme you too in the ref
club with @forki , @eiriktsarpalis and @mexx ? sigh 😄
@eiriktsarpalis You also have to add the guards for the portable version in the tests, like @forki did. Btw. IMHO the new test is less readable as the tests provided by Steffen. The main problem is that you actually check multiple properties in one test. Just my 2c. |
|
||
let scont (result : 'T option) = | ||
match result with | ||
| Some _ when Interlocked.Increment exnCount = 1 -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's safe to have an inpure function Interlocked.Increment
inside when
clause?
It's not possible is evalued more than once? is not better to have it after -> ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be surprised if it did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only possibility for this to happen would be to have the continuation fire more than once, but that's hardly related to the use of when
guards per se.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better not to do this in a when clause.
let mkOp (index : int) = function | ||
| NoneResultAfter t -> returnAfter t (fun () -> None) | ||
| SomeResultAfter t -> returnAfter t (fun () -> Some index) | ||
| ExceptionAfter t -> returnAfter t (fun () -> raise <| ChoiceExn index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think <|
is not used much raise (ChoiceExn index))
instead?
@mexx the test basically defines an algebra that models all possible inputs that could be passed to Async.Choice. The test then executes the input and verifies that it indeed is up to spec. Not really a unit test, but it certainly puts FsCheck into good use. |
@eiriktsarpalis I've got the idea. FsCheck is pretty cool, I also use it a lot. |
@mexx agree here. Ideally if that test breaks, you don't know which "property" is broken - they should ideally be one test per property so if any given test fails, you know which behaviour is playing up. You can still use FSCheck for each of those tests, of course :-) |
@mexx I wouldn't consider this a property test, even if it does use FsCheck. It does not check against 6 separate "properties". It's merely branching out depending on the outcome of the computation. It's really just a specification test. |
@eiriktsarpalis however you call it. The problem is like @isaacabraham said. |
@mexx I guess I'm trying to say that the assertion that this tests separate invariants is incorrect. It's really just modelling all possible inputs to the implementation, checking that the output is up to spec in each case. |
static member Choice(computations : Async<'T option> seq) : Async<'T option> = | ||
unprotectedPrimitive(fun args -> | ||
let result = | ||
try Choice1Of2 <| Seq.toArray computations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please just use a forward pipe. We don't use much back piping in the F# compiler or FSharp.Core,
@@ -268,6 +268,21 @@ namespace Microsoft.FSharp.Control | |||
/// <returns>A computation that returns an array of values from the sequence of input computations.</returns> | |||
static member Parallel : computations:seq<Async<'T>> -> Async<'T[]> | |||
|
|||
/// <summary>Creates an asynchronous computation that executes all given asynchronous computations in parallel, | |||
/// returning the result of the first succeeding computation (i.e. the first computation with a result that is 'Some x').</summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This summary is a bit long. Normally it is one sentence, no i.e.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I basically just copied the format from the corresponding Async.Parallel segment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i dont know if better, but returning blabla
can be put inside returns xmldoc element
like <returns>the result of the first succeeding computation (i.e. the first computation with a result that is 'Some x'</returns>
it's used by xmldoc generator i think
/// If cancelled, the computation will cancel any remaining child computations but will still wait | ||
/// for the other child computations to complete.</remarks> | ||
/// <param name="computations">A sequence of computations to be parallelized.</param> | ||
/// <returns>A computation that returns the first succeeding computation in the sequence of input computations.</returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the sequence
can be misleading, as it can imply the order is preserved, what is not the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, we need to use wording in which 'first' clearly indicates the first to complete, as opposed to the first in the sequence. Any suggestions?
Thank you for this contribution. |
@eiriktsarpalis We have this test failing intermittently on windows, it may indicate a race condition:
|
As requested, this PR re-implements Async.Choice as presented in #744.
I've rewritten the code so that the internal async implementations are utilised, and have included a unit test that verifies the implementation using FsCheck.