Skip to content
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

Merged
merged 10 commits into from
Jan 20, 2016
Merged

Implement Async.Choice #807

merged 10 commits into from
Jan 20, 2016

Conversation

eiriktsarpalis
Copy link
Member

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.

@msftclas
Copy link

Hi @eiriktsarpalis, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!

In order for us to evaluate and accept your PR, we ask that you sign a contribution license agreement. It's all electronic and will take just minutes. I promise there's no faxing. https://cla.microsoft.com.

TTYL, MSBOT;

@forki
Copy link
Contributor

forki commented Dec 17, 2015

Awesome! What about the tests that I added? Do you think it would make sense to include these as well?

@msftclas
Copy link

@eiriktsarpalis, Thanks for signing the contribution license agreement so quickly! Actual humans will now validate the agreement and then evaluate the PR.

Thanks, MSBOT;

@eiriktsarpalis
Copy link
Member Author

@forki Yeah, I've seen them. The FsCheck test covers a superset of those.

@forki forki mentioned this pull request Dec 17, 2015
@forki
Copy link
Contributor

forki commented Dec 17, 2015

Cool. I closed my PR in favor of this one.
I assume you still need to merge that line from the surface area test.

match result with
| Choice2Of2 edi -> args.aux.econt edi
| Choice1Of2 [||] -> args.cont None
| Choice1Of2 [|P body|] -> body args
Copy link
Member Author

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?

Copy link
Contributor

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.

@eiriktsarpalis
Copy link
Member Author

@forki what line was that?

@forki
Copy link
Contributor

forki commented Dec 17, 2015

@eiriktsarpalis
Copy link
Member Author

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
Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Contributor

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 😄

@mexx
Copy link
Contributor

mexx commented Dec 17, 2015

@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 ->
Copy link
Contributor

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 -> ?

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Contributor

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)
Copy link
Contributor

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?

@eiriktsarpalis
Copy link
Member Author

@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.

@mexx
Copy link
Contributor

mexx commented Dec 17, 2015

@eiriktsarpalis I've got the idea. FsCheck is pretty cool, I also use it a lot.
Maybe there is a way to model the different properties the implementation should hold as separate checks?
Currently there are as far as I can see six different cases that gets checked and those are the properties IMO.

@isaacabraham
Copy link
Contributor

@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 :-)

@eiriktsarpalis
Copy link
Member Author

@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.

@mexx
Copy link
Contributor

mexx commented Dec 17, 2015

@eiriktsarpalis however you call it. The problem is like @isaacabraham said.
I think even the specification can be broken up into separate invariants which should hold.
The outcome is dependent on the input, right? So with properly chosen input the output should be only in one category. That should be the properties to test for.

@eiriktsarpalis
Copy link
Member Author

@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
Copy link
Contributor

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>
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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>
Copy link
Contributor

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.

Copy link
Member Author

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?

@KevinRansom KevinRansom merged commit 1da20e4 into dotnet:master Jan 20, 2016
@KevinRansom
Copy link
Member

Thank you for this contribution.

@eiriktsarpalis eiriktsarpalis deleted the async-choice branch January 21, 2016 14:06
@dsyme
Copy link
Contributor

dsyme commented Jul 21, 2016

@eiriktsarpalis We have this test failing intermittently on windows, it may indicate a race condition:

[00:41:46] 1) Error : FSharp.Core.Unittests.FSharp_Core.Microsoft_FSharp_Control.AsyncModule.Async.Choice specification test
[00:41:46] System.Exception : Falsifiable, after 58 tests (0 shrinks) (StdGen (1910982604,296182551)):
[00:41:46] Original:
[00:41:46] ChoiceWorkflow
[00:41:46]   ([SomeResultAfter -7; SomeResultAfter 4; NoneResultAfter -1; ExceptionAfter -2;
[00:41:46]     NoneResultAfter -4; ExceptionAfter -10; ExceptionAfter 9; NoneResultAfter -9;
[00:41:46]     NoneResultAfter 10; SomeResultAfter -8],Some -14)
[00:41:46] with exception:
[00:41:46] NUnit.Framework.AssertionException:   Expected: 4000

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants