Open akarnokd opened 6 years ago
Here's a bookmark for some of the side-convo that is going on in the Slack channel.
https://reactivex.slack.com/archives/C02B9R3QA/p1525433788000119
I'd appreciate if, over time as we figure out how to work together, less discussions happened on Slack and more on GitHub. It's early days of rebuilding the community, and right now so we don't need much structure but for progressing something like this more formal structure will be required.
We definitely want to be conservative when adding operators and each one will need to be debated on merit in their own separate issue. We aren't ready for separate issues though. Let's use this thread as the baseline for bringing parity and discussions if we even need to do so (vs creating libraries) and how it could be done and when would be best to do it.
You are doing a fantastic job. Thank-you for your help so far. Thank-you for doing this audit @akarnokd.
I would like to propose yet another operator from RxJs, which I find highly useful with UI applications!
exhaustMap
- it is a high order observable which, unlike switch
(and switchMap
) ignores any new values pushed from the upstream until the inner observable has completed.
@RivaCode. We have it as flatMapDrop
or flatMapLatest
in the RxJava 2 Extensions project. I didn't include those because they look as rare to esoteric even in RxJava's quite liberal eyes.
Can't say I agree.
When, for example:
Clicking a button, causes a creation of a resource and you must make sure that operation is atomic.
exhaustMap
(or flatMapDrop
/flatMapLatest
) comes quite handy!
As I mentioned in the Slack channel, thanks to C# extension methods, these or any other operator can live in any 3rd party library, outside of Rx.NET. The drawback is the possible difficulty of discovering them, the distrust of being non-standard or naming conflicts between multiple 3rd party extension methods.
Added section about subjects in RxJava.
Would it be possible to apply the performance improvments of concatMap
behind the scences? So that if you write Concat().Select(x => ...)
it will use the improved implementation. Similar as it is done for Skip(4).Skip(2)
, which will be transposed to Skip(6)
.
It's Select(x => IObservable).Concat()
but you'd have to typematch the Select
instance with the appropriate Func<T, IObservable<R>>
signature.
I am hesitant about introducing the vocabulary of other Rx variants into the dotnet repo, since it will introduce ambiguity. For example, flatMap
already exists, it's called SelectMany
, and I don't think introducing aliases will help .NET developers understand the duality between IEnumerable
and IObservable
.
I think concatMap
and switchMap
are really useful, giving you more intuition of the possible ways to reduce the sequence, so I think they do have a place in this library. I would align their names with SelectMany
though, and call them ConcatMany
and SwitchMany
.
@glopesdev It probalby should be ConcatSelect
because the counter part of map
is Select
. Or SelectConcat
because flatMap
translates to SelectMany
and not ManySelect
.
@akarnokd I see. So you would need some type variable in the type checks, which are not possible like (pseudo code):
if (source is Select<x, y>.Selector && y is IObservable) ...
@quinmars ConcatSelect
breaks the correspondence as you pointed out, so it doesn't really help users immediately relate to these variants. SelectConcat
could be a reasonable option, but it doesn't sound right because it doesn't preserve the original flavour of SelectMany
, which comes from SQL:
from x select one
turned into
from x select (many)
.
In the case of Concat
, it feels more intuitive to read:
from x concat (many)
.
Another way to think about this:
Select
doesn't really translate cleanly to Map
. It comes from SQL, where it originally meant selecting which columns of the data frame to include in the query result. Gradually it was realized it could be much more powerful and extended to include derived columns created from "mapping" or "transforming" the columns of each row.
In a similar way, the Many
in SelectMany
doesn't really mean Merge
or Flat
, but simply indicates that you are selecting multiple rows, rather than just one row. What to do with these rows is left up to imagination. In database "pull" world, there is usually only one way to think about it, because time is irrelevant, so the only thing you can do with Many
is "select" them.
In reactive world, there is many more things you can do with Many
, you can ConcatMany
or SwitchMany
, among other possibilities.
I'm aware of the meaning of SelectMany
. But as you said: it's called SelectMany
, because you select not only one element but many elements. With Switch
you switch to the latest observable, but with SwitchMany
you do not switch to many observables. The only difference is that you pass a selector to select the latest obervable. IMO Many
does not make any sense here. Same with Concat
you always conact many observables, even if the method is only called Concat
. ConcatMany
would be just confusing.
I see that the concatenation of two verbs like in ConcatSelect
or SelectConcat
is not ideal, but SwitchMany
feels simply wrong. Maybe SelectingSwitch
and SelectingConcat
? Or do we real need an extra name? Couldn't an additional overload do the same work? Like .Switch(v => v.Observable)
or .Concat(async x => await GetAsync(x))
.
Couldn't an additional overload do the same work? Like .Switch(v => v.Observable) or .Concat(async x => await GetAsync(x))
I think this really nails it 👍 i very much support the overload idea, no need to confuse people with more names that seem to imply new concepts
Hmmm, although I still agree with @quinmars solution of adding an overload, I wanted to present another perspective into using the Many
suffix.
For me, what Many
represents is really the selector, as was pointed out. Specifically, Many
in this case indicates a constraint on the selector: it cannot be just any selector, in fact it has to be a selector "from one to many", it has to return an IObservable
or IEnumerable
.
Basically Many
represents the qualification on the return type of the selector, while the verb itself represents what you are doing with those Many
: in the case of SelectMany
you are simply emitting all of them, which is why Merge
is used; in the case of ConcatMany
or SwitchMany
you are applying the Concat
or Switch
operator to the result of the selector.
I think it's equally valid to think about things this way, and as long as this perspective is made clear, and used consistently, I would still accept ConcatMany
and SwitchMany
.
The selector overload idea is not too bad, but it will beg the question of why SelectMany
is not simply an overload of Merge
...
I think it'd be good to consider new operators on a case-by-case basis. So, I'd be all for seeing a set of issues that propose addition of operators in a more fine-grained manner.
A few answers:
ISubject<T>
operators, but a base class could be introduced if we feel really strong about such universal additions. However, we could also await C# 8.0's introduction of default interface members and ponder pros and cons of this approach (and evaluate if it'd do the job of course).SelectMany
's shorter overloads really correspond to calling the larger overload with identity or similarly trivial functions).System.Reactive.Aliases
and was a nightmare and highly undesirable (it can only add confusion for people already in the .NET ecosystem to see many more overloads that turn out to be aliases and feel straight unfamiliar). This is a .NET project, and it should align with .NET-isms, vocabulary, and style.IObservable<T>
objects are runtime). I'd rather invest in peephole optimizations similar to fusion of Where
and Select
, or coalescing of Skip
and Take
operators in LINQ to Objects.Just bumped into the necessity for a SwitchIfEmpty
.
Has been asked on SO too.
Posted separately: #1076.
What we really need to move forward when adding new extensions is a code analysis of existing repositories to find out, which operators are most used.
BTW, I would love to see feature parity with Rx.JS rather than Rx.Java as they are used much often.
I've collected the operators and overloads of theirs which are available in RxJava but (likely) not in Rx.NET yet to enable discussions about feature synchronization.
Operators
combineLatestDelayError
concatDelayError
concatEager
concatEagerDelayError
error(Callable)
fromCallable
generate
intervalRange
mergeDelayError
switchOnNextDelayError
zip(delayError)
blockingIterable
blockingSubscribe
cache
collect
compose
concatMap
concat
. Can be implemented more efficiently thanconcat(map(source, f))
.concatMapDelayError
concatMapEager
concatEager
.concatMapEagerDelayError
concatMapIterable
concatMap
ping anIterable
turned into an observable.doAfterNext
doFinally
doOnDispose
doOnSubscribe
flatMap(delayError)
merge(map(source, f))
.forEachWhile
reduceWith
repeat(BooleanSupplier)
repeatWhen
retry(Predicate)
retry(BiPredicate)
retryUntil(BooleanSupplier)
retryWhen
sample(emitLast)
scanWith
switchIfEmpty
switchMap
switchOnNext(map(source, f))
.switchMapDelayError
takeUntil(Predicate)
observeOn(delayError)
unsubscribeOn
withLatestFrom(Observable...)
autoConnect
ConnectableObservable
after the specified number of observers have subscribed.refCount(n, time)
ConnectableObservable
after the specified number of observers have subscribed and/or disconnect after a grace period (under review).Subjects
RxJava has one additional subject type:
UnicastSubject
. It buffers items until one observer subscribes some time later and then drains the buffer into the observer.In addition,
Subject
s in RxJava have additional state-peeking methods available to all variants:hasObservers()
true
if there are currently any observers subscribed to the subject.hasComplete()
true
if the subject terminated normally.hasThrowable()
true
if the subject terminated exceptionally.getThrowable()
toSerialized()
onXXX
methods threadsafe to call from any thread.hasValue()
true
if aBehaviorSubject
orReplaySubject
have items in their buffer.getValue()
BehaviorSubject
.getValues()
ReplaySubject
.I'm not sure how interface evolution works in C#, so assuming it would break
System.Reactive.Interfaces
users, the methods could be added to a new interface derived fromISubject
or just onto the various RxSubject
types.