-
Notifications
You must be signed in to change notification settings - Fork 429
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 withLatest(from:) (Rx.withLatestFrom) #128
Conversation
Do you have an example with more meaningful names than |
@sharplet let enabled1 = MutableProperty<Bool>(true)
let enabled2 = MutableProperty<Bool>(true)
let buttonTap: Signal<(), NoError> = ...
buttonTap
.sample(from: enabled1.producer)
.sample(from: enabled2.producer) // this signal is `Signal<((Void, Bool), Bool), NoError>`
.filter { $0.1 && $1 }
.observe { _ in print("hello") } // prints only when both `enabled1` & `enabled2` are true I think this is hard to implement if only existing |
That example seems like it'd be better with let enabled1 = MutableProperty<Bool>(true)
let enabled2 = MutableProperty<Bool>(true)
let buttonTap: Signal<(), NoError> = ...
SignalProducer
.combineLatest(enabled1.producer, enabled2.producer)
.sample(on: buttonTap)
.filter { $0 && $1 }
.observe { _ in print("hello") } // prints only when both `enabled1` & `enabled2` are true |
@mdiep /// Stores API response items.
let items = MutableProperty<[Item]>([])
items <~ buttonTap
.sample(from: urlTextField.reactive.text)
.map { APIRequest($1) }
.flatMap(.merge) { sendRequest($0) }
.sample(from: items.producer)
.map { $1 + [$0.item] }
items.signal
.observeValues { print("items updated: \($0)") } |
Anyway, the dominant signal of above cases is |
I definitely think the
But your example still doesn't seem ideal. In particular, I wouldn't use
|
And with the recent improvements to let url = Property(initial: nil, then: urlTextField.reactive.text)
// Only enabled when input url is non-nil
let submit: Action<(), [Item], Error> = Action(input: url) { url in
// url is the current value when the button is tapped
let request = APIRequest(url)
return sendRequest(request)
}
// bind button to submit action:
// - button is disabled while executing
// - button is disabled if input is invalid
button.reactive.pressed = CocoaAction(submit)
let items = Property(
initial: [],
then: submit.values.scan([]) { $1 + $0 }
)
items.signal
.observeValues { print("items updated: \($0)") }
// you can also handle errors gracefully by observing `submit.errors` |
@mdiep By the way, your code has difficulty when @sharplet |
Now that you phrase it in terms of data flow, I see your point. This flows pretty well for me at the point of use: items <~ buttonTap
.withLatest(from: textField.reactive.continuousTextValues)
.flatMap(.latest) { api.getItems($1) }
// etc. |
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 👍, but it needs some changes and also requires tests.
/// sampled (possibly multiple times) by `self`, then terminate | ||
/// once `self` has terminated. **`samplee`'s terminated events | ||
/// are ignored**. | ||
public func sample<U>(from samplee: Signal<U, NoError>) -> Signal<(Value, U), Error> { |
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 think withLatest(_ samplee: Signal<U, NoError>)
would be clearer.
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.
Alternative: combineLatest(from:)
.
Edit: IMO with
is not directional, but from
implies a direction.
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 is different from combineLatest
since it only sends a new value when one of the signals changes. So it should have a different name.
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 prefer withLatest(_:)
because "with" sounds like a small addition to the main stream, and receiver remains as the first class that also implies the rank direction.
Maybe withLatest(from:)
can be a compromise, but I think we don't need this "from".
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.
withLatest(_:)
should be fine.
withLatest(textField.reactive.continuousTextValues)
reads like "with latest text values" to me. Edit: That's said the from
label would give a stronger implication of "with the latest (one) from".
/// sampled (possibly multiple times) by `self`, then terminate | ||
/// once `self` has terminated. **`samplee`'s terminated events | ||
/// are ignored**. | ||
public func sample<U>(from samplee: SignalProducer<U, NoError>) -> Signal<(Value, U), Error> { |
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 should be removed. We don't expose mixed operators like this. If someone wants to use a Signal
with a SignalProducer
, they need to convert the Signal
to a SignalProducer
.
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.
We already have mixed operators like:
combineLatest<U>(with other: SignalProducer<U, Error>)
combineLatest<U>(with other: Signal<U, Error>)
flatMap<U>(Value -> SignalProducer<U, Error>)
flatMap<U>(Value -> Signal<U, Error>)
I think there are also needs for withLatest(signal)
case too.
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.
We do not have hot + cold -> hot
except for flattening operators though. Only on SignalProducer
we have overloads that take Signal
s directly.
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.
Is it illegal to have different type for the argument?
I think hot -> T -> hot
or cold -> T -> cold
keeps the same structure, and it's good enough.
(This is probably the "endofunctor" thing in category theory after the partial application of T
, though arguments are often flipped in Swift.)
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.
It is not illegal. Just that hot -> cold -> hot
doesn't seem something very commonly used, except for flattening operators (Signal<SignalProducer, E>
).
/// sampled (possibly multiple times) by `self`, then terminate | ||
/// once `self` has terminated. **`samplee`'s terminated events | ||
/// are ignored**. | ||
public func sample<U>(from samplee: Signal<U, NoError>) -> SignalProducer<(Value, U), Error> { |
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 should also be removed.
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 is for producer though. We have a couple of few cold -> hot -> cold
producer operators.
} | ||
|
||
return disposable | ||
} |
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.
Can this be implemented with sample(with:)
?
return samplee.sample(with: self).map { ($1, $0) }
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.
To achieve the simplest map { ($0, stateProperty.value) }
alternative as possible, I think returning signal should not get interfered with any of samplee
's terminal events.
sample(with:)
, on the other hand, will wait for both sampler
and samplee
's .completed
to finally emit its .completed
, so this PR is not just a flipped sample(with:)
.
For comparison, in RxSwift, samplee
's .completed
is ignored but .failed
is forwarded.
(As a side note, I have restricted samplee
's error type as NoError
only for now, allowing a future extension to also support samplee
with non-NoError
types when decision (ignore or forward error) is settled.)
(Nit) // Button taps with (the) latest continuous text values.
buttonTaps.withLatest(textField.reactive.continuousTextValues)
// Button taps with (the) latest (one) from (the) continuous text values
buttonTaps.withLatest(from: textField.reactive.continuousTextValues) |
I agree, and think |
OK, it seems 2:2 now, but I can go for |
Travis CI has had a huge backlog in Mac jobs since a day or two ago. |
I'm 👍 with |
Great! Renaming is done in 95bd754, and previous test has finally passed. |
/// nothing happens. | ||
/// | ||
/// - parameters: | ||
/// - samplee: A signal that its latest value is sampled by `self`. |
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 think this would read better as "A signal whose latest value..."
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 in 9580123.
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
This PR is RAC port of
Rx.withLatestFrom
(namely,sample(from:)
) withsamplee
's error type constrained toNoError
only.Diagram: http://rxmarbles.com/#withLatestFrom
This was originally discussed in ReactiveCocoa/ReactiveCocoa#2082 (not accepted), but I found this operator very useful because it is easy to fetch current state (
MutableProperty
) viasourceSignal
:Above code is much easier to read compared to:
or:
Please note that
sample(from:)
is NOT actually a flipped version ofsample(with:)
since it discardssamplee
's any terminal events.For
.completed
handling, this behavior is same asRx.withLatestFrom
(and is often easy to use).The code is originally from: https://github.com/inamiy/ReactiveAutomaton/blob/28027a656b923008f34089c739e51cdbe13b65cb/Sources/ReactiveCocoa%2BSampleFrom.swift