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

Moved on operator to Signal #2572

Merged
merged 2 commits into from Nov 26, 2015
Merged

Moved on operator to Signal #2572

merged 2 commits into from Nov 26, 2015

Conversation

NachoSoto
Copy link
Member

Note that Signal.on is equivalent to SignalProducer.on except for the started event, which wouldn't make sense on Signal.

I also added a test to verify that disposed is invoked at the right time.

@NachoSoto NachoSoto added this to the 4.0 milestone Nov 25, 2015
@NachoSoto NachoSoto closed this Nov 25, 2015
@NachoSoto NachoSoto reopened this Nov 25, 2015
@NachoSoto
Copy link
Member Author

Flaky test is flaky. The only failing test is the one in RACSequenceSpec which is unrelated to this change.

@neilpa
Copy link
Member

neilpa commented Nov 25, 2015

I don't understand why we need this for Signal. It doesn't make sense to "inject" effects on a signal. It's already hot and you can just observe it. If the goal is to have an onDisposed handler (which I'm not sure there's much value in) that should be an isolated thing.

@NachoSoto
Copy link
Member Author

For the most part that's true. However, sometimes you do want to synchronize side effects across multiple subscriptions, and you can't do that with multiple subscriptions!

Example:

originalSignal
  .observeNext(sideEffects1)

originalSignal
  .skipRepeats()
  .flatMap(.Latest, transform: f)
  .observeNext(sideEffects2)

If you need to guarantee that sideEffects1 always precedes sideEffects2 the above code is fragile.
Instead, one should do this:

originalSignal
  .on(next: sideEffects1)
  .skipRepeats()
  .flatMap(.Latest, transform: f)
  .observeNext(sideEffects2)

So that's why I did this.

@neilpa
Copy link
Member

neilpa commented Nov 25, 2015

That's true, but is this really a pattern we should encourage by adding it to the public API? My gut says no given the nuances with trying to explain the differences between observe and on.

@NachoSoto
Copy link
Member Author

given the nuances with trying to explain the differences between observe and on.

That's an important difference, for sure, but given that the semantics are the same as in SignalProducer between on and start (ignoring the differences between observe and start), and the fact that it's a common operator in ReactiveExtensions, I don't think it's a concern.

I don't know about it being a pattern we should encourage, but it's certainly something necessary.
To put my example in words:
You have a stream of values. From this stream, you're making an HTTP request for every value (say using .Merge). You want to perform side effects for every emitted value, as well as for every result emitted by the network request.

Without this addition there is no way to do this in a pure way without a potential risk for race conditions.

@neilpa
Copy link
Member

neilpa commented Nov 25, 2015

given that the semantics are the same as in SignalProducer between on and start (ignoring the differences between observe and start)

There's a relation, but we can't ignore the difference between observe and start here because that's the entire point of on. It attaches effects to a producer that run each time it's started. This doesn't correlate to signals in quite the same way that other operators do.

it's a common operator in ReactiveExtensions

But they don't distinguish between hot and cold signals.

From this stream, you're making an HTTP request for every value (say using .Merge). You want to perform side effects for every emitted value, as well as for every result emitted by the network request.

The most common effect being logging which I'd feel comfortable adding. Anything else and you're probably better served by trying to integrate whatever you're doing into the signal pipeline functionally. This just doesn't feel like a problem we should attempt to solve given the other problems around complexity and understanding it introduces.

@ReactiveCocoa/reactivecocoa Any other opinions?

@NachoSoto
Copy link
Member Author

But they don't distinguish between hot and cold signals.

I know, but this is something that users of Rx still need to keep in mind. Using on is not necessarily doing anything if there are no clients of the stream. Just saying that this is not that hard to understand for newcomers.

Anything else and you're probably better served by trying to integrate whatever you're doing into the signal pipeline functionally.

How would you suggest implement what I described "integrating it into the signal pipeline functionally"? If you give me an alternate implementation, then I completely agree with everything you're saying. I'd rather not add a new operator if don't have to, but the case I'm trying to make is that it does solve a real problem.

The most common effect being logging which I'd feel comfortable adding.

That's another example of something that users will want to do very often:

signal
  .someOperator()
  .on(next: { print("Log 1: \($0)") })
  .flatMapErrors( ... )
  .on(error: { print("Error: \($0)") })
  .otherOperator
  .on(next: { print("Log 2: \($0)") })

You can accomplish this by separating the signal into intermediate values (because in this case, potential race conditions don't really matter that much I suppose), but that's VERY annoying in practice and this operator would make it a lot easier to do.

@neilpa
Copy link
Member

neilpa commented Nov 26, 2015

How would you suggest implement what I described "integrating it into the signal pipeline functionally"? If you give me an alternate implementation

I'm not sure what you're scenario is but it seems like behavior that should be captured in a custom operator and not a generic escape hatch.

That's another example of something that users will want to do very often

but that's VERY annoying in practice and this operator would make it a lot easier to do.

How often are you actually doing this? Outside of logging (which I've made custom operators for) I've not had a need for this. I'd much prefer to solve the logging issue directly and not add another operator that's nearly the same as observe

@jspahrsummers
Copy link
Member

I agree with @neilpa's objections, but at the same time, it seems weird to offer an operator for SignalProducer that would also be (mostly) valid for Signal, and not provide the latter. I think we should add it.

@NachoSoto
Copy link
Member Author

Especially since it solves a real problem. @neilpa you haven't answered my questions: if you can come up with a way to solve the issue above, then maybe there is no need for this, but otherwise this is actually useful. You can see the "real-world" example that made me add this: NachoSoto/AsyncImageView@3981351

With the double subscription, since the producer returned by flatMap can emit synchronously, there was a race condition (and it was easily reproducible because, the image view would be empty!). This change allows a user to expression that sort of flow (logging intermediate values being another example) in a more correct way.

@neilpa
Copy link
Member

neilpa commented Nov 26, 2015

I see, you're deriving from UIImageView which limits your flexibility. I'm not sure why you need to clear the image on every incoming request though instead of waiting for the result. But given that requirement and the derivation I don't see an alternative.

I still think this is a niche operator and would prefer to just solve the logging problem. But it looks like I'm in the minority here.

neilpa added a commit that referenced this pull request Nov 26, 2015
@neilpa neilpa merged commit 9339bbb into master Nov 26, 2015
@jspahrsummers
Copy link
Member

@neilpa neilpa deleted the signal-on branch November 26, 2015 18:53
@neilpa
Copy link
Member

neilpa commented Nov 26, 2015

I'm sorry Toby

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

Successfully merging this pull request may close these issues.

None yet

3 participants