-
-
Notifications
You must be signed in to change notification settings - Fork 419
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
Make working with promises of promises easier #3813
Conversation
Adds `next_unwrap` method to `Promise`. `next_unwrap` makes working with types like `Promise[Promise[Foo]]` much easier to work with compared to using `next`. The idea for `next_unwrap` was my idea while working on a Pony client for the GitHub REST API. The implementation itself was written by Joe McIlvain.
This needs to be squashed once approved. I'll take care of that. I have a GitHub REST API library that an early version of will be released after this is merged and I do a ponyc release. |
Are we settled on the naming here? In most contexts I see a method like this is almost always named as |
I think Joe's next_unwrap is a good name in the context. I think that flat_next is probably in the same ballpark as next_unwrap for "i might guess what this does" if you don't have the FP background. Of the two that Jason mentioned, i find next_flatten to be preferable. Of the existing and Jason's two names, I'm pretty apathetic. Any of the three is about the same to me. @jemc you did the naming from my original "when", do you have a preference? I have a library to release that is waiting on this, so i'd like to make any decision quickly. |
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.
No problems with this addition. Few changes suggested for formatting and wording. I also am wondering why the test does not have an assertion for both parts of the chain both "start" and "end" values?
packages/promises/_test.pony
Outdated
start("start") | ||
inter("end") | ||
|
||
primitive _NextUnwrapHelper | ||
fun promise_string(s: String): Promise[String] => | ||
// fake do something with string, return promise of another | ||
Promise[String] | ||
|
||
fun complete(h: TestHelper, s: String) => | ||
h.assert_eq[String]("end", s) |
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 see an assertion for "end" here, but not one for "start" -- should a "start" assertion be added?
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.
Start exists entirely so we can have something to call next_unwrap
on. Think testing its value in promise_string
makes the case that we are testing less clear.
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.
Fair enough. My rationale was from a perspective of "tests as documentation" to show where in the process "start" is accessible. For the same reason of "tests as documentation" I agree that as it is makes it clear where "end" is accessible -- which is the additional functionality here.
Co-authored-by: Ryan A. Hagenson <[email protected]>
Co-authored-by: Ryan A. Hagenson <[email protected]>
Co-authored-by: Ryan A. Hagenson <[email protected]>
Co-authored-by: Ryan A. Hagenson <[email protected]>
Co-authored-by: Ryan A. Hagenson <[email protected]>
Co-authored-by: Ryan A. Hagenson <[email protected]>
I approve of using So I'd be good with |
@jasoncarr0 what do you think of |
at the moment however i have renamed to |
packages/promises/promise.pony
Outdated
inner.next[None]( | ||
{(fulfilled: B) => | ||
p(fulfilled) | ||
}) |
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.
Shouldn't we handle rejection from inner
here and reject p?
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 not sure what you mean. we handle in the else below.
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.
If we reach line 216 then there are no more sources of errors, so we'll never reach the else
block.
But inner
is still a future we need to respect. If inner
rejects, then so should this method, but it never will, since this next call isn't passing a reject callback and so is using the default that does nothing on a reject and hence the returned lambda will never accept nor reject if inner
is produced but fails.
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.
Yeah, I think Jason is correct about the missing 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.
Sorry @jasoncarr0 I'm still not getting it.
Do you have code that you think fixes the issue? Perhaps seeing the proposed fix will help me understand what you are talking about.
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.
@jemc given that I didnt write the code, if you have time to try and figure out how to cover the two holes, I can test it. But I'm going to take a shot at the missing case I see although I'm not sure how to cover it.
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.
}) | |
}, {() => p.reject()}) |
So lesson learned make a suggestion first as that's a lot faster than trying to convey it otherwise
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.
Ah I see Joe made the suggestion down below and you got it working so we're good here
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.
Looks like a case might be missing?
Co-authored-by: Ryan A. Hagenson <[email protected]>
I realized that my test case is totally bogus and I'm trying to figure out how to actually test this. Its non-trivial. |
ok figured it out. god that test did NOTHING. dur. |
correction, i figured some out. i have other tests that are failing and... i can try out with real world stuff from my library that this started in and they work, so... arg. |
@jemc we definitely missed a case. There outer promise is always using RejectAlways which doesnt send down the chain to the inner. |
packages/promises/promise.pony
Outdated
{(fulfilled: B) => | ||
p(fulfilled) | ||
}) | ||
else |
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.
@SeanTAllen - I think something like this is the right fix that @jasoncarr0 was pointing out.
I didn't try compiling it so there might be something slightly wrong but it looks correct to me.
{(fulfilled: B) => | |
p(fulfilled) | |
}) | |
else | |
{(fulfilled: B) => p(fulfilled) }, | |
{() : B? => p.reject(); error }) | |
else |
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.
that sadly doesnt compile
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.
/home/sean/code/ponylang/ponyc/packages/promises/promise.pony:226:13: argument not a subtype of parameter
{() : B? => p.reject(); error})
^
Info:
/home/sean/code/ponylang/ponyc/packages/promises/promise.pony:226:13: argument type is {(): B ?}[A #share, B #share] iso^
{() : B? => p.reject(); error})
^
/home/sean/code/ponylang/ponyc/packages/promises/promise.pony:48:5: parameter type is Reject[None val] iso^
rejected: Reject[B] = RejectAlways[B])
^
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.
fixed
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 compiles:
{(fulfilled: B) => p(fulfilled)},
{()? => p.reject(); error})
@jasoncarr0 @jemc what is the test case for the issue that Jason pointed out? |
@jasoncarr0 can you review the new tests and tell me if you think anything is missing? |
This test also fails:
The issue as can be seen with https://playground.ponylang.io/?gist=4b958c4198f8986b2b95d554b8476dcc is that an error in fulfill apparently shouldnt cause that same reject to occur only the ones later down the chain. That's an existing bug that we didn't replicate. Nope. Its a bug on our side. I fixed it. |
@jemc one of the bug fixes though means that we dont need rejected in the fulfill block (that makes sense) so it should be easier to set up the reject handler wrapper although i am struggling with it at the moment. |
Ok I have all my tests passing. @jasoncarr0 please review my tests and see what cases you think are missing and ill add. |
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.
Test contents look good but the completes can be eager for some of the checks
packages/promises/_test.pony
Outdated
|
||
let start = Promise[String] | ||
let inter = start.flatten_next[String]( | ||
_FlattenNextStartHelper~error_fulfill(h, initial_string), |
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 error fulfill method up here seems to have a slight problem because this isn't the end of the test,
but error_fulfill marks complete(true)
packages/promises/_test.pony
Outdated
fun error_fulfill(h: TestHelper, expected: String, actual: String) ? => | ||
h.assert_eq[String](expected, actual) | ||
h.complete_action(expected) | ||
h.complete(true) |
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 completion down here could end the test early for line 189
h.complete(true) |
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.
No, this code is correct. This isn't called as part of the test in 189. There are two different primitives, one for Start, one for Inter. If this completion was on Start and not Inter that would be true.
|
||
fun reject_expected(h: TestHelper, action: String) => | ||
h.complete_action(action) | ||
h.complete(true) |
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.
Similarly this can complete too early e.g.at line 238
h.complete(true) |
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.
same as above, there are two reject_expected
. One for Start and one for Inter. This is the one for Inter and the test is complete when reject_expected is called. There is also a reject_expected on the Start primitive that doesn't complete.
Note there are no complete
calls on _FlattenNextStartHelper
@jasoncarr0 I believe this is ready to be squashed and merged. See my replies re: tests, those |
I did some renaming of things in the test and because this is fairly complicated, added english lanuage descriptions of what is and is not expected for each test. |
I think this is ready for a squash and merge. Once approved, I'll do the squash so I can update the commit comment to match the changes this underwent since the PR was opened. |
Co-authored-by: Joe Eli McIlvain <[email protected]>
Adds
next_unwrap
method toPromise
.next_unwrap
makes workingwith types like
Promise[Promise[Foo]]
much easier to work withcompared to using
next
.The idea for
next_unwrap
was my idea while working on a Pony clientfor the GitHub REST API. The implementation itself was written by
Joe McIlvain.