-
Notifications
You must be signed in to change notification settings - Fork 720
Add EventLoopFuture fold function #269
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
Conversation
|
Can one of the admins verify this patch? |
Sources/NIO/EventLoopFuture.swift
Outdated
| } | ||
|
|
||
| extension EventLoopFuture { | ||
| /// Returns a new `EventLoopFuture` that fires only when this `EventLoopFuture` and all the provided futures complete. |
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 description doesn't really adequately capture what this method is for. It's not wrong at all, it just misses what this method is actually for. Mind rewording it?
Sources/NIO/EventLoopFuture.swift
Outdated
| /// - eventLoop: The `EventLoop` on which the new `EventLoopFuture` callbacks will fire. | ||
| /// - with: The combining function used to produce partial results. | ||
| /// - returns: A new `EventLoopFuture`. | ||
| public func fold<U>(_ futures: [EventLoopFuture<U>], eventLoop: EventLoop, with combiningFunction: @escaping (EventLoopFuture<T>, EventLoopFuture<U>) -> EventLoopFuture<T>) -> EventLoopFuture<T> { |
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.
Hrm, is this exactly what we want this method to be? This method is not far from being a trivial wrapper over reduce, and there's a limited amount of utility here. I appreciate we may have been talking past each other here, but I was thinking we'd have something a bit closer to the Haskell construct, where the signature of the combiningFunction is (T, U) -> EventLoopFuture<T>.
I have no particular objection to the function as written, but I suspect the construct is of limited utility (you already have the array of futures, you can already call reduce on them, all fold does is wrap that into a new promise you just allocated).
Does that make sense?
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 totally agree now that I think about it, I think I missed that point (T, U) -> EventLoopFuture<T>.
I will modify this to reflect what we want it to be.
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.
👍 Thanks
|
@Lukasa Ok I updated the changes |
Lukasa
left a comment
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.
one minor note, but so far this looks great.
Sources/NIO/EventLoopFuture.swift
Outdated
| /// | ||
| /// The returned `EventLoopFuture` will fail with the first error encountered, as soon as this | ||
| /// `EventLoopFuture` or any of the provided futures fail: otherwise, it will succeed only when all | ||
| /// of them do. |
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.
Ok, so let's address this comment to clarify exactly what will happen here. We should reword to:
The returned
EventLoopFuturewill fail as soon as the a failure is encountered in any of thefutures(or in this one). However, the failure will not occur until all precedingEventLoopFutures have completed. At the point the failure is encountered, all subsequentEventLoopFutureobjects will no longer be waited for. This function therefore fails fast: once a failure is encountered, it will immediately fail the overallEventLoopFuture.
Sources/NIO/EventLoopFuture.swift
Outdated
| return combiningFunction(f1Value, f2Value) | ||
| }) | ||
| } | ||
| return body.hopTo(eventLoop: eventLoop) |
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.
@Lukasa do we need to hopTo the specified eventLoop, I am feeling like this could be removed as the combiningFunction should take care of that, right?! and will ensure that the final EventLoopFuture is hopTo the eventLoop of self. And combiningFunction will allow the caller to hopTo a different one if desired.
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 combining function will not take care of that, which is actually a really good point.
I think all the combiningFunction invocations should occur on the same event loop, and that event loop should be the one that self belongs to. This means we actually need a hopTo on the future returned by the combiningFunction to make it continue to execute on self's event loop.
Sources/NIO/EventLoopFuture.swift
Outdated
| /// - eventLoop: The `EventLoop` on which the new `EventLoopFuture` callbacks will fire. | ||
| /// - with: A function that will be used to fold the values of two `EventLoopFuture`s and return a new value wrapped in an `EventLoopFuture`. | ||
| /// - returns: A new `EventLoopFuture`. | ||
| public func fold<U>(_ futures: [EventLoopFuture<U>], eventLoop: EventLoop, with combiningFunction: @escaping (T, U) -> EventLoopFuture<T>) -> EventLoopFuture<T> { |
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.
instead of the eventLoop parameter, you can just use self.eventLoop
| let newFuture = f1.and(f2).then({ (args: (T, U)) -> EventLoopFuture<T> in | ||
| let (f1Value, f2Value) = args | ||
| assert(self.eventLoop.inEventLoop) | ||
| return combiningFunction(f1Value, f2Value) |
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.
@Lukasa Do we really need a hopTo before we return the EventLoopFuture from the combiningFunction? I think then accepts a callback whose returned future runs on any eventLoop and executes this callback on its caller's eventLoop and always returns a future on its caller's eventLoop (the result of and) which already (because of and) hopTo its caller's eventLoop, so if we start the reduce with self, it will always return a future whose callbacks run on self.eventLoop and always executes the callbacks on self.eventLoop.
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, you're right, the EventLoopFuture returned from then does not need to be hopped: it is tied to the loop of f1. The assert is sufficient.
Want to remove the parentheses from the then call? They shouldn't be needed.
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 yes
|
@Lukasa Ok any further notes? |
Lukasa
left a comment
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.
LGTM, @weissi mind reviewing as well?
weissi
left a comment
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 pretty good already, small changes needed though
| XCTAssertTrue(f.isFulfilled) | ||
| } | ||
|
|
||
| func testFoldWithSuccessAndAllSuccesses() throws { |
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.
could we have a test case that folds EventLoopFutures from different actual event loops (not EmbeddedEventLoops)?
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.
Yes sure let me add one
| let body = futures.reduce(self) { (f1: EventLoopFuture<T>, f2: EventLoopFuture<U>) -> EventLoopFuture<T> in | ||
| let newFuture = f1.and(f2).then { (args: (T, U)) -> EventLoopFuture<T> in | ||
| let (f1Value, f2Value) = args | ||
| assert(self.eventLoop.inEventLoop) |
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.
but what if we're dealing with different event loops?
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.
@weissi It doesn't matter. and's then will execute on f1's event loop, and the first f1 is self, and then the future returned from combiningFunction will be moved over to the f1 loop (because that's how then works.
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 yes, that makes sense. Thank you!
| XCTAssertTrue(f.isFulfilled) | ||
| } | ||
|
|
||
| func testFoldWithMultipleEventLoops() throws { |
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.
@weissi Does this test suffice for multiple eventLoops?
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.
yes, thanks!
Motivation: Add primitive for folding futuristic values. Modifications: Add a new EventLoopFuture extension containing the implementation of `fold<U>(futures:combiningFunction)`. Result: An API for foding futures.
weissi
left a comment
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.
thanks very much @Lukasa and @karim-elngr , looks great!
| let body = futures.reduce(self) { (f1: EventLoopFuture<T>, f2: EventLoopFuture<U>) -> EventLoopFuture<T> in | ||
| let newFuture = f1.and(f2).then { (args: (T, U)) -> EventLoopFuture<T> in | ||
| let (f1Value, f2Value) = args | ||
| assert(self.eventLoop.inEventLoop) |
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 yes, that makes sense. Thank you!
|
@swift-nio-bot test this please |
Motivation:
#184 Add primitive for folding futuristic values.
Modifications:
Add a new EventLoopFuture extension containing the implementation of
fold<U>(futures:combiningFunction).Result:
An API for folding futures.