-
Notifications
You must be signed in to change notification settings - Fork 7.6k
2.0 Design: Naming #2787
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
Comments
I personally am not a fan of Similar with RxJava/1 The various Java 8 functions are a tricky one. I don't like how confusing they are. We named ours in a way that they sort correctly in docs, they are more descriptive, and we also support arities beyond 3. I think we should first explore whether the
|
Java 8 functional interfaces indicate the purpose not just the shape, but it is a matter of taste. If lambdas are used, it doesn't really matter except when the user has some interface Action0 extends Runnable { default void run() { call(); } void call(); } This helps if one wants to send an Action0 to a executor but not the other way around. Still, there could be name conflicts between Func0/Action0 and the higher arity functions when compared to JDK types: For discoverability, we may define Func0 on top of Supplier and so on, but the method naming will be inconsistent across arities. One thing I'd like to emphasize is the use of Predicate instead of Func1<T, Boolean>. I've read somewhere that the latter boxing/unboxing is not always optimized away by HotSpot and we get a reference equality check everywhere instead of just a CPU flag test. It is hard to decide: an independent set of |
I'm thinking about this right now ... wondering if I still think this:
However, what I'm wondering is whether we should have both -> Is this something we should consider and pursue? |
The name conflict is a smaller issue compared to the re-learning of the entry class name and scrub all the historical knowledge. I don't believe there will be many that use both versions in the same project and those who switch have to revisit every use place due to the package/API changes anyway. Let's stick to |
What about different types to represent hot and cold? |
That is what I have been proposing for a while, and what I am pursuing with RxMobile, since it only needs to deal with semi realtime hot streams that have no intrinsic support for back-pressure. Semantically, conceptually, and scenario-wise It is not Like any collection, both types support the same kind of operators like KISS FTW |
Been thinking more about this. I conceptually like the idea of them being separate, and In Rx.Net In RxJava, where we're targeting environments with event loops like Netty, @headinthebox I know that you argue that unbounded buffers are not always bad, and I agree that they aren't always bad. I think they're just fine when a developer opts in to it. I do however think they are bad when hidden, and don't feel comfortable reverting the decisions of 1.x to allow hidden unbounded buffers again. So, "KISS FTW" sounds nice, but it doesn't address the real issue of not-simple-to-debug silent latency and memory bloat of unbounded buffers. I have experienced this with RxJava 0.x pre-backpressure in production systems. So, if we do have an |
Let me state upfront that I do not understand why Netty excludes using synchronization, but that is my total ignorance on how Netty works. However, I think that you can (roughly) achieve the same effect as non-blocking merge by doing an If that is true, then the use of a buffer becomes explicit due to the (BTW with async code there is no such thing as "simple-to-debug" ;-) |
It's not just Netty. Anything using event loops, including Synchronization of 2 event loops together would result in 1 thread being blocked. That is an absolute no for RxJava to cause.
I focused on
There are gradients of "simplicity", it's not black-or-white. I'd rather encapsulate as much complexity in the implementation as possible so operating it is simpler. I'd also rather require a little more thought at time of creating an I think the biggest mistake in RxJava v1 after adding backpressure was continuing to let the raw Unless we don't offer cc @stevegury and @tmontgomery who I consulted with on this topic in case either of them want to weigh in. |
I've opened a new issue to move this conversation about bounded/unbounded: #3213 |
If this is so important for you, then we should only have [I remain of the opinion that it is absolutely OK that it is the developer's responsibility to make sure their app runs under the required memory & time constrains, and that given we are in a Turing complete world, unbounded buffers are no different than any other unbounded use of memory, or time. But obviously I am alone in this viewpoint, so will happily surrender to the majority vote.] |
@headinthebox Is this perspective driven by theoretical purity about |
|
That seems to be a purely theoretical and subjective argument then, not one based on functionality. Is there functionality or behavior that causes problems? |
From a design standpoint, I feel that separating out the backpressure/hot stream concept is a huge point. One of the most confusing things to get your head round in Rx is the concept of backpressure since the distinction between them is very blurred in both the docs and the actual implementation. From a technical perspective, I can see it being a burden to maintain possibly two concurrent sets of operators but I'm approaching this purely from a design standpoint. |
It was mentioned to me that it may help to restate what I'm seeking for here as it could be interpreted as a theoretical and polarized argument. Good point, so I'll try. It is somewhat long. I am open to having 2 types in RxJava to represent bounded/unbounded, hot/cold, Observable/Flowable. However, I do want solid reasons for the decisions, as we spent over a year debating many proposals in the 0.x phase before landing on an I and others then spent many months of debate formalizing the pattern in Reactive Streams, demonstrating across many companies and projects that a async stream type with reactive-pull backpressure is valid and wanted. Those types are now proposed for inclusion in JDK 9 (j.u.c.Flow), demonstrating fairly broad agreement. In the time since while using the reactive-pull That said, I also recognize that in many environments, an I prefer not making users have to choose between two virtually identical types ( If we do have two types, here is how I would envision them co-existing: Observable (no backpressure, unbounded operators)Creation is as expected, emit however one wishes: create(s -> {
onNext(t)
}) Cold generation though should be discouraged, and should return a Observable.from(iterable) -> Flowable That case should just return a The I suggest that An observable.toFlowable(Strategy.DROP). Flowable (backpressure, bounded operators)The Flowable.createHot(s -> {
s.onNext(...)
}, Strategy.FAIL)
// cold sync generator
Flowable.createSync(...)
// cold async generator
Flowable.createAsync(...) API DesignHow would these two types be used when producing public APIs in libraries? I think it will be more confusing than today where this is just one type unless it is very clear that /**
* Observable signals a "hot" stream where you must account for flow control yourself or risk unbounded latency and memory growth.
*/
Observable<T> getStuff();
/**
* Flowable signals a "cold" or "hot" stream that will adapt its flow control, or emit an error if it overwhelms your consumption.
*/
Flowable<T> getStuff2(); The QuestionWhat functional, performance or usability items warrant making people choose between 2 very similar types, when Is the "confusion" of backpressure in v1 I believe it is just a usability issue of I think it's possible to have a single type that is easy to use for all of these use cases ("hot", "cold", backpressured, not-backpressured), and it's purely an implementation detail of the operators. I think it would be far more confusing to choose when to expose an Then again, perhaps it is worth communicating via a type that something is "hot": #2785 Community ... please provide your insight. |
Do we have the capacity to maintain 4 versions at the same time (1.x, 2.x backpressure, 2.x unbounded, Single)? I wouldn't use the name |
We can likely support Java 9 with 2.x without giving up Java 8 compatibility with multi-release jar files: http://openjdk.java.net/jeps/238 I intend on implementing Flow and Publisher (if the versioned jar feature works). |
The capacity to maintain multiple variants is a very good question, and valid for decision making. Particularly when all use cases are addressed by 1 implementation. |
Though, most operators would behave the same on both. We could probably make most of this just a public API division with most of the implementation sharing. If it makes sense to have two types. |
Here is an attempt at separating out behavior between
Below I separate out the operators (such as temporal flow control) and creators/generators as applicable to each type. I'd appreciate feedback on this direction. ObservableThis is for creating a true "push" Observable without backpressure
The
A key question is whether This would exist to convert to a Flowable
The following should all either not exist on Observable, or return Flowable instead
These would have unbounded buffering-
I don't think
we could however have something like this
these would go away
these should return a
Most of the other operators make sense to leave as they are, but I've stopped itemizing them for brevity. Perhaps we can continue using FlowableThis is for creating a "push-pull" Flowable with backpressure
We should also have generators for the common (and hard to implement) cases (these are being designed in 1.x right now)
The
but we can also have the
The following should all either not exist on Observable, or return Flowable instead
These would have bounded buffering-
these should return a
I question any of the following existing on
Most of the other operators make sense to leave as they are, but I've stopped itemizing them for brevity. |
Additionally, I think |
I'd estimate a 75% of the operators require 3 implementations as backpressure awareness is subtle in many operators and guaranteed single value by itself allows different optimizations. I guess we still want high performance for Flowable, Observable and Single, right? The fact that there isn't one Subscriber type for all of them drags in individual XXXOperator<R, T> implementations (interface FlowableOperator, SingleOperator, ObservableOperator). My question is that will these be prerequisites to a release or can we publish RS backpressure supporting Flowable and do the rest in 2.1? |
Yes we will want high performance for each. But internal implementations of operators is definitely something that can evolve and change over time after we release 2.0. The Flowable operators are the ones that can be leveraged across all 3 by injecting unbounded buffering and using Long.MAX_VALUE. It won't achieve the possible performance gains for Observable and Single, but those can come as we have time. Additionally, the perf and implementation differences will be most significant in merge, observeOn, and a handful of others. The rest are mostly synchronous, without buffers, and will have little gain if any from the change behind the MAX_VALUE fast-paths we already do (such as filter, map, take). To get the public APIs right with Observable, Flowable, and Single, I suggest we use the Flowable operator implementations for them all and focus on API, correct behavior, composition, and stability. Then as we go we will end up with custom operators as needed (merge will happen sooner for example).
Getting the relationship between the types is very important and will affect the public API of each over, thus I view this is an essential thing to get done before 2.0 is released since we can't change APIs of the Flowable type in 2.1, and as my examples above show, it will definitely cause Flowable to change. |
I think the naming has come to a reasonable standing. |
Since we are aiming at Java 9 and
j.u.c.Flow
as the base API, we have the opportunity to use Java-native functional interfaces instead of our Func and Action interfaces, plus rename other components.I propose the following changes:
Observable
->Flowable
XXXSubject
->XXXProcessor
XXXSubscription
->XXXDisposable
Func0
->j.u.c.f.Supplier
Func1
->j.u.c.f.Function
Func1<T, Boolean>
->j.u.c.f.Predicate
Func2
->j.u.c.f.BiFunction
Func3..FuncN
->rx2.functions.TriFunction
orrx2.functions.Function3
etc.Action0
->Runnable
Action1
->j.u.c.f.Consumer
Action2
->j.u.c.f.BiConsumer
Action3..ActionN
->rx2.functions.TriConsumer
orrx2.functions.Consumer3
etc.The text was updated successfully, but these errors were encountered: