Skip to content

Conversation

@karim-elngr
Copy link
Contributor

@karim-elngr karim-elngr commented Apr 3, 2018

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.

@swift-nio-bot
Copy link

Can one of the admins verify this patch?

}

extension EventLoopFuture {
/// Returns a new `EventLoopFuture` that fires only when this `EventLoopFuture` and all the provided futures complete.
Copy link
Contributor

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?

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

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?

Copy link
Contributor Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Thanks

@karim-elngr
Copy link
Contributor Author

@Lukasa Ok I updated the changes

Copy link
Contributor

@Lukasa Lukasa left a 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.

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

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 EventLoopFuture will fail as soon as the a failure is encountered in any of the futures (or in this one). However, the failure will not occur until all preceding EventLoopFutures have completed. At the point the failure is encountered, all subsequent EventLoopFuture objects will no longer be waited for. This function therefore fails fast: once a failure is encountered, it will immediately fail the overall EventLoopFuture.

return combiningFunction(f1Value, f2Value)
})
}
return body.hopTo(eventLoop: eventLoop)
Copy link
Contributor Author

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.

Copy link
Contributor

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.

/// - 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> {
Copy link
Member

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

@karim-elngr karim-elngr Apr 4, 2018

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.

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes

@karim-elngr
Copy link
Contributor Author

@Lukasa Ok any further notes?

Copy link
Contributor

@Lukasa Lukasa left a 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?

Copy link
Member

@weissi weissi left a 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 {
Copy link
Member

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

Copy link
Contributor Author

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

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?

Copy link
Contributor

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.

Copy link
Member

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

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?

Copy link
Member

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.
Copy link
Member

@weissi weissi left a 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)
Copy link
Member

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!

@weissi
Copy link
Member

weissi commented Apr 4, 2018

@swift-nio-bot test this please

@Lukasa Lukasa added the 🆕 semver/minor Adds new public API. label Apr 5, 2018
@Lukasa Lukasa added this to the 1.4.0 milestone Apr 5, 2018
@Lukasa Lukasa merged commit d58fa23 into apple:master Apr 5, 2018
@karim-elngr karim-elngr deleted the thenreduce branch April 5, 2018 12:38
@karim-elngr
Copy link
Contributor Author

@weissi and @Lukasa, thanks guys for all the help! Time to celebrate my first merged PR!! 😄

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

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants