Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Alternative Rx bindings for Scala
The previous RxScala binding attempt to optimize for seamless interop between Scala and Java.
The intended interop is illustrated by the following example where in Scala a class is defined that takes
an
Observable[Movie]
that is transformed using RxScala operators:which is then called in Java, passing a Java
Observable<Movie>
to the constructorThe technique used to obtain this transparency is to use a value class with a private constructor that implements
the Rx operators in an idiomatic Scala way, and a companion object that is used to construct instances in Scala
Since
rx.lang.scala.Observable[T] extends AnyVal
, the underlying representation ofrx.lang.scala.Observable[T]
is the same as
rx.Observable<T>
. Becauserx.lang.scala.Observable[T]
is an opaque type in Scala,the Scala programmer only sees the Scala-friendly operators.
However, in the current the illusion of interop is quickly lost when going beyond this simple example.
For example but type
Notification[T]
andScheduler[T]
are defined using wrappers,and hence they are not compatible with
Notification<T>
respectivelyScheduler<T>
.For instance, when materializing an
Observable[T]
in Scala to anObservable[Notification[T]]
,we lost the seamless interop with
Observable<Notification<T>>
on the Java side.However, the real problems with seamless interop show up when we try to creating bindings for other Rx types.
In particular types that have inheritance or more structure.
For example, RxScala currently defines a type synonym
type Observer[-T] = rx.Observer[_ >: T]
,but no further bindings for observers.
Similarly, for subjects RxScala defines
type Subject[-T, +R] = rx.subjects.Subject[_ >: T, _ <: R]
.The problem with these definitions is that on the Java side, subjects are defined as:
without binding any of the Rx subjects.
The consequence is that
Subject[S,T]
in Scala is unrelated torx.lang.scala.Observable[T]
in Scala,but shows up as a
rx.Observable[T]
. The problem however is that if we want to expose subjects in Scalasuch that they derive from both
Observable[S]
andObserver[T]
we cannot use theextend AnyVal
trickwe used for
Observable[T]
and immediately lose transparent interop with Java.The problem is even worse because
AsyncSubject<T>
,BehaviorSubject<T>
, … all derive fromSubject<T,T>
,so if we want them to derive from a common base
Subject[T,T]
type in Scala we lose transparency for those as well.And again, if we expose the various subjects by extending
AnyVal
, they are useless in Scala because they do not inheritfrom a common base type. To avoid implementing all methods of observable and observer on each specific subject
we might add implicit conversions to
Observable[T]
andObserver[T]
but that still does not give Scala usersa native
Subject[S,T]
type.The inheritance problem is not just limited to subjects, but also surfaces for subscriptions.
Rx scala currently defines
type Subscription = rx.Subscription
using a type synonym as well,and we run into exactly the same problems as with subjects when we try to bind the
various Rx subscriptions
BooleanSubscription
,SerialSubscription
, etc.Since we cannot wrap Rx types in Scala such that they are both (a) transparently interoperable with Java,
and (b) feel native and idiomatic to Scala, we should decide in favor of optimizing RxScala for Scala
and consumption of Rx values from Java but not for Scala as a producer.
The new bindings feel like a completely native Scala library, without needing any complications of the Scala side.
You pay the price when crossing the Scala/Java interop boundary, which is where it should be.
The proper way is to put the burden of interop on the Scala side, in case you want to create
a reusable Rx-based library in Scala, or wrap and unwrap on the Java side.
Delegation versus Inheritance
The obvious thought is that using delegation instead of inheritance (http://c2.com/cgi/wiki?DelegationIsInheritance)
will lead to excessive wrapping, since all Scala types wrap and delegate to an underlying RxJava implementation.
Note however, that the wrapping happens at query generation time and incurs no overhead when messages are flowing
through the pipeline. Say we have a query
xs.map(f).filter(p).subscribe(o)
. Even though the Scala types are wrappers,the callback that is registered with xs is something like
x => { val y = f(x); if(p(y)){ o.asJavaObserver.onNext(y) }}
and hence there is no additional runtime penalty.