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

Add FlattenStrategy.race #233

Merged
merged 2 commits into from
Apr 2, 2017

Conversation

inamiy
Copy link
Contributor

@inamiy inamiy commented Jan 17, 2017

Implemented #138.

Ref: ReactiveX - Amb operator

I'm still wondering whether .first is really a good naming since there's already a blocking func first().
I personally feel race is a better name.

@RuiAAPeres
Copy link
Member

RuiAAPeres commented Jan 17, 2017

(I personally feel race is a better name)

flatten(.🚗)

@andersio
Copy link
Member

andersio commented Jan 17, 2017

The context & use site of the strategy & the operator are different enough not to be confusing IMO.

@RuiAAPeres
Copy link
Member

Could it be called simply .amb? From a discoverability point of view, it would be easier (searching for Functional Reactive Programming amb would yield the right sort of information). For people that already know the .amb operator the name would be fine as well.

@sharplet
Copy link
Contributor

What does "amb" actually mean? I personally feel it would be better to have a more intention-revealing name, and link back to the equivalent Rx version.

@inamiy
Copy link
Contributor Author

inamiy commented Jan 17, 2017

(Build failure seems unrelated)

I also don't know what amb actually means, so I'm +1 for either .first or .race.
.race is more descriptive (used in RxPHP?), but .first is also good name in contrast to .latest.

@mdiep
Copy link
Contributor

mdiep commented Jan 18, 2017

I'm guessing amb is short for ambivalent?

Edit: Nope, it's should for ambiguous. https://msdn.microsoft.com/en-us/library/hh211783(v=vs.103).aspx#Anchor_1

@@ -31,6 +31,15 @@ public enum FlattenStrategy: Equatable {
/// The resulting producer will complete only when the producer-of-producers
/// and the latest producer has completed.
case latest

/// Only the events from the first input producer should be considered for
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a little ambiguous. Should be "from the first input producer to send an event".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated comments in cf6c4eb.

@@ -733,6 +754,124 @@ private struct LatestState<Value, Error: Swift.Error> {
var replacingInnerSignal: Bool = false
}

extension SignalProtocol where Value: SignalProducerProtocol, Error == Value.Error {
/// Returns a signal that forwards values from the first signal sent on
/// `self`, ignoring values sent on consecutive inner signals.
Copy link
Contributor

Choose a reason for hiding this comment

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

Likewise, I don't think this is an accurate description.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated comments in cf6c4eb.


private struct FirstState<Value, Error: Swift.Error> {
var outerSignalComplete: Bool = false
var innerSignalComplete: Bool = true
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems a little weird to default this to true. It might be better to default to false and check for innerSignalComplete || active == nil in shouldComplete?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for this catch! I could remove this redundant flag now in 819550f.

case .completed:
let shouldComplete: Bool = state.modify {
$0.outerSignalComplete = true
return $0.active == nil
Copy link
Contributor

Choose a reason for hiding this comment

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

What should the behavior be if the outer signal receives a signal, completes, but the inner signal hasn't sent an event?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I haven't written the test yet, but I think the result should be the same as:

it("should complete when the outer signal completes before sending any signals").
https://github.com/ReactiveCocoa/ReactiveSwift/pull/233/files#diff-9cd786f9e52e786771d96cf8c42d3befR1481

New test will be: it("should complete when the outer signal completes before inner signals send any event").

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I will also update ambiguous test titles too)

@mdiep mdiep added this to the 2.0 milestone Jan 22, 2017
@mdiep
Copy link
Contributor

mdiep commented Jan 22, 2017

I think I like race.

first conflicts with the instance method, but it's also a little ambiguous about whether it's the first signal to be sent or the first signal to send an event. I suppose you could also get confused with race like that—but it seems a little more likely to direct you to the documentation.

@andersio andersio modified the milestone: 2.0 Jan 23, 2017
@inamiy
Copy link
Contributor Author

inamiy commented Jan 24, 2017

OK, I will rename to .race.

@inamiy
Copy link
Contributor Author

inamiy commented Jan 24, 2017

In 9630f9f, I had to fix .completed behavior because scenario like #138 (comment) will complete immediately and this would be totally useless.

To fix that, I had to restore additional state var innerSignalComplete: Bool = true, and this flag basically checks the existence of at least one signal been started (doesn't care about value emission).

New .completed behavior of SignalProducer.flatten(.race)

  • If outer producer completes before any inner producer is created, complete immediately.
  • If outer producer completes after inner producer is created, complete after inner producer is completed.
  • If inner producer completes, complete after outer producer is completed.

@mdiep
Copy link
Contributor

mdiep commented Jan 25, 2017

This is a breaking change because it adds a case to an existing enum.

This either needs to wait until we're ready for 2.0 on master or it needs to target a 2.0 branch.

@mdiep mdiep added this to the 2.0 milestone Jan 25, 2017
@inamiy inamiy changed the title Add FlattenStrategy.first Add FlattenStrategy.race Jan 25, 2017
@andersio
Copy link
Member

The PR should target master-next in this case, hmm?

@mdiep
Copy link
Contributor

mdiep commented Mar 31, 2017

@inamiy Would you mind resolving the conflicts here? I think this is ready to be merged to master for a 2.0 release.

@NachoSoto
Copy link
Member

Sorry if I'm late commenting on this, but does this really add value? Isn't this equivalent to:

signal
  .take(first: 1)
  .flatten(anyStrategy)

@andersio
Copy link
Member

andersio commented Mar 31, 2017

No, your snippet takes only one inner signal and flattens it. race accepts any arbitrary amount of inner signals, flattens the first one that sends events, and disposes of the rest.

@mdiep
Copy link
Contributor

mdiep commented Mar 31, 2017

Sorry if I'm late commenting on this, but does this really add value? Isn't this equivalent to:

Discussion here last time you asked this. 😉

@NachoSoto
Copy link
Member

OMG lol, that's embarrassing @mdiep 😓

@NachoSoto
Copy link
Member

Ship it then :D

@inamiy
Copy link
Contributor Author

inamiy commented Mar 31, 2017

Rebase done in 76d457e 😉 (but CI alive?)

@andersio
Copy link
Member

andersio commented Apr 1, 2017

Reopen to trigger CI.

@andersio andersio closed this Apr 1, 2017
@andersio andersio reopened this Apr 1, 2017
guard isWinningSignal else { return }

// Dispose all running innerSignals except winning one.
disposableHandle.remove()
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to make these not run for every event? They just need to be run once after all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 1b83292.


innerSignal.observe { [unowned innerSignal] event in

let isWinningSignal: Bool = state.modify {
Copy link
Member

Choose a reason for hiding this comment

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

Please name the arguments for multi-line closures.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 1b83292.


switch event {
case .completed:
let shouldComplete: Bool = state.modify {
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

observer.send(error: error)

case .completed:
let shouldComplete: Bool = state.modify {
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

@andersio andersio merged commit 6f6787c into ReactiveCocoa:master Apr 2, 2017
@inamiy inamiy deleted the FlattenStrategy.first branch April 4, 2017 09:29
@inamiy inamiy mentioned this pull request Mar 6, 2019
1 task
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.

None yet

6 participants