Skip to content

Commit 65b3f6f

Browse files
Merge pull request ReactiveX#598 from Applied-Duality/RebaseLatestChanges
New Scala Bindings
2 parents c2cd326 + da560ac commit 65b3f6f

36 files changed

+1232
-442
lines changed

language-adaptors/rxjava-scala/Rationale.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ and consumption of Rx values from Java but not for Scala as a producer.
9191
If we take that approach, we can make bindings that feels like a completely native Scala library,
9292
without needing any complications of the Scala side.
9393
```scala
94-
object Observer { …}
94+
object Observable { …}
9595
trait Observable[+T] {
9696
def asJavaObservable: rx.Observable[_ <: T]
9797
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
RxScala Release Notes
2+
=====================
3+
4+
This release of the RxScala bindings builds on the previous 0.15 release to make the Rx bindings for Scala
5+
include all Rx types. In particular this release focuses on fleshing out the bindings for the `Subject` and `Scheduler`
6+
types, as well as aligning the constructor functions for `Observable` with those in the RxJava.
7+
8+
Expect to see ongoing additions to make the Scala binding match the equivalent underlying Java API,
9+
as well as minor changes in the existing API as we keep fine-tuning the experience on our way to a V1.0 release.
10+
11+
Observer
12+
--------
13+
14+
In this release we have made the `asJavaObserver` property in `Observable[T]`as well the the factory method in the
15+
companion object that takes an `rx.Observer` private to the Scala bindings package, thus properly hiding irrelevant
16+
implementation details from the user-facing API. The `Observer[T]` trait now looks like a clean, native Scala type:
17+
18+
```scala
19+
trait Observer[-T] {
20+
def onNext(value: T): Unit
21+
def onError(error: Throwable): Unit
22+
def onCompleted(): Unit
23+
}
24+
25+
object Observer {...}
26+
```
27+
28+
To create an instance of a specific `Observer`, say `Observer[SensorEvent]` in user code, you can create a new instance
29+
of the `Observer` trait by implementing any of the methods that you care about:
30+
```scala
31+
val printObserver = new Observer[SensorEvent] {
32+
override def onNext(value: SensorEvent): Unit = {...value.toString...}
33+
}
34+
```
35+
or you can use one of the overloads of the companion `Observer` object by passing in implementations of the `onNext`,
36+
`onError` or `onCompleted` methods.
37+
38+
Note that typically you do not need to create an `Observer` since all of the methods that accept an `Observer[T]`
39+
(for instance `subscribe`) usually come with overloads that accept the individual methods
40+
`onNext`, `onError`, and `onCompleted` and will automatically create an `Observer` for you under the covers.
41+
42+
While *technically* it is a breaking change make the `asJavaObserver` property private, you should probably not have
43+
touched `asJavaObserver` in the first place. If you really feel you need to access the underlying `rx.Observer`
44+
call `toJava`.
45+
46+
Observable
47+
----------
48+
49+
Just like for `Observer`, the `Observable` trait now also hides its `asJavaObservable` property and makes the constructor
50+
function in the companion object that takes an `rx.Observable` private (but leaves the companion object itself public).
51+
Again, while *technically* this is a breaking change, this should not have any influence on user code.
52+
53+
```scala
54+
trait Observable[+T] {
55+
def subscribe(observer: Observer[T]): Subscription = {...}
56+
def apply(observer: Observer[T]): Subscription = {...}
57+
...
58+
}
59+
object Observable {
60+
def create[T](func: Observer[T] => Subscription): Observable[T] = {...}
61+
...
62+
}
63+
```
64+
65+
The major changes in `Observable` are wrt to the factory methods where too libral use of overloading of the `apply`
66+
method hindered type inference and made Scala code look unnecessarily different than that in other language bindings.
67+
All factory methods now have their own name corresponding to the Java and .NET operators
68+
(plus overloads that take a `Scheduler`).
69+
70+
* `def from[T](future: Future[T]): Observable[T]`,
71+
* `def from[T](iterable: Iterable[T]): Observable[T]`,
72+
* `def error[T](exception: Throwable): Observable[T]`,
73+
* `def empty[T]: Observable[T]`,
74+
* `def items[T](items: T*): Observable[T],
75+
* Extension method on `toObservable: Observable[T]` on `List[T]`.
76+
77+
In the *pre-release* of this version, we expose both `apply` and `create` for the mother of all creation functions.
78+
We would like to solicit feedback which of these two names is preferred
79+
(or both, but there is a high probability that only one will be chosen).
80+
81+
* `def apply[T](subscribe: Observer[T]=>Subscription): Observable[T]`
82+
* `def create[T](subscribe: Observer[T] => Subscription): Observable[T]`
83+
84+
Subject
85+
-------
86+
87+
The `Subject` trait now also hides the underlying Java `asJavaSubject: rx.subjects.Subject[_ >: T, _<: T]`
88+
and takes only a single *invariant* type parameter `T`. all existing implementations of `Subject` are parametrized
89+
by a single type, and this reflects that reality.
90+
91+
```scala
92+
trait Subject[T] extends Observable[T] with Observer[T] {}
93+
object Subject {
94+
def apply(): Subject[T] = {...}
95+
}
96+
```
97+
For each kind of subject, there is a class with a private constructor and a companion object that you should use
98+
to create a new kind of subject. The subjects that are available are:
99+
100+
* `AsyncSubject[T]()`,
101+
* `BehaviorSubject[T](value)`,
102+
* `Subject[T]()`,
103+
* `ReplaySubject[T]()`.
104+
105+
The latter is still missing various overloads http://msdn.microsoft.com/en-us/library/hh211810(v=vs.103).aspx which
106+
you can expect to appear once they are added to the underlying RxJava implementation.
107+
108+
Compared with release 0.15.1, the breaking changes in `Subject` for this release are
109+
making `asJavaSubject` private, and collapsing its type parameters, neither of these should cause trouble,
110+
and renaming `PublishSubject` to `Subject`.
111+
112+
Schedulers
113+
----------
114+
115+
The biggest breaking change compared to the 0.15.1 release is giving `Scheduler` the same structure as the other types.
116+
The trait itself remains unchanged, except that we made the underlying Java representation hidden as above.
117+
as part of this reshuffling, the scheduler package has been renamed from `rx.lang.scala.concurrency`
118+
to `rx.lang.scala.schedulers`. There is a high probability that this package renaming will also happen in RxJava.
119+
120+
```scala
121+
trait Scheduler {...}
122+
```
123+
124+
In the previous release, you created schedulers by selecting them from the `Schedulers` object,
125+
as in `Schedulers.immediate` or `Schedulers.newThread` where each would return an instance of the `Scheduler` trait.
126+
However, several of the scheduler implementations have additional methods, such as the `TestScheduler`,
127+
which already deviated from the pattern.
128+
129+
In this release, we changed this to make scheduler more like `Subject` and provide a family of schedulers
130+
that you create using their factory function:
131+
132+
* `CurrentThreadScheduler()`,
133+
* `ExecutorScheduler(executor)`,
134+
* `ImmediateScheduler()`,
135+
* `NewThreadScheduler()`,
136+
* `ScheduledExecutorServiceScheduler(scheduledExecutorService)`,
137+
* `TestScheduler()`,
138+
* `ThreadPoolForComputationScheduler()`,
139+
* `ThreadPoolForIOScheduler()`.
140+
141+
In the future we expect that this list will grow further with new schedulers as they are imported from .NET
142+
(http://msdn.microsoft.com/en-us/library/system.reactive.concurrency(v=vs.103).aspx).
143+
144+
To make your code compile in the new release you will have to change all occurrences of `Schedulers.xxx`
145+
into `XxxScheduler()`, and import `rx.lang.scala.schedulers` instead of `rx.lang.scala.schedulers`.
146+
147+
Subscriptions
148+
-------------
149+
150+
The `Subscription` trait in Scala now has `isUnsubscribed` as a member, effectively collapsing the old `Subscription`
151+
and `BooleanSubscription`, and the latter has been removed from the public surface. Pending a bug fix in RxJava,
152+
`SerialSubscription` implements its own `isUnsubscribed`.
153+
154+
155+
```scala
156+
trait Subscription {
157+
def unsubscribe(): Unit = { ... }
158+
def isUnsubscribed: Boolean = ...
159+
}
160+
161+
object Subscription {...}
162+
```
163+
164+
To create a `Subscription` use one of the following factory methods:
165+
166+
* `Subscription{...}`, `Subscription()`,
167+
* `CompositeSubscription(subscriptions)`,
168+
* `MultipleAssignmentSubscription()`,
169+
* `SerialSubscription()`.
170+
171+
In case you do feel tempted to call `new Subscription{...}` directly make sure you wire up `isUnsubscribed`
172+
and `unsubscribe()` properly, but for all practical purposes you should just use one of the factory methods.
173+
174+
Notifications
175+
-------------
176+
177+
All underlying wrapped `Java` types in the `Notification` trait are made private like all previous types. The companion
178+
objects of `Notification` now have both constructor (`apply`) and extractor (`unapply`) functions:
179+
180+
```scala
181+
object Notification {...}
182+
trait Notification[+T] {
183+
override def equals(that: Any): Boolean = {...}
184+
override def hashCode(): Int = {...}
185+
def apply[R](onNext: T=>R, onError: Throwable=>R, onCompleted: ()=>R): R = {...}
186+
}
187+
```
188+
The nested companion objects of `Notification` now have both constructor (`apply`) and extractor (`unapply`) functions:
189+
```scala
190+
object Notification {
191+
object OnNext { def apply(...){}; def unapply(...){...} }
192+
object OnError { def apply(...){}; def unapply(...){...} }
193+
object OnCompleted { def apply(...){}; def unapply(...){...} }
194+
}
195+
```
196+
To construct a `Notification`, you import `rx.lang.scala.Notification._` and use `OnNext("hello")`,
197+
or `OnError(new Exception("Oops!"))`, or `OnCompleted()`.
198+
199+
To pattern match on a notification you create a partial function like so: `case Notification.OnNext(v) => { ... v ... }`,
200+
or you use the `apply` function to pass in functions for each possibility.
201+
202+
There are no breaking changes for notifications.
203+
204+
Java Interop Helpers
205+
--------------------
206+
207+
Since the Scala traits *wrap* the underlying Java types, yoo may occasionally will have to wrap an unwrap
208+
between the two representations. The `JavaConversion` object provides helper functions of the form `toJavaXXX` and
209+
`toScalaXXX` for this purpose, properly hiding how precisely the wrapped types are stored.
210+
Note the (un)wrap conversions are defined as implicits in Scala, but in the unlikely event that you do need them
211+
be kind to the reader of your code and call them explicitly.
212+
213+
```scala
214+
object JavaConversions {
215+
import language.implicitConversions
216+
217+
implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = {...}
218+
implicit def toScalaNotification[T](s: rx.Notification[_ <: T]): Notification[T] = {...}
219+
implicit def toJavaSubscription(s: Subscription): rx.Subscription = {...}
220+
implicit def toScalaSubscription(s: rx.Subscription): Subscription = {...}
221+
implicit def scalaSchedulerToJavaScheduler(s: Scheduler): rx.Scheduler = {...}
222+
implicit def javaSchedulerToScalaScheduler(s: rx.Scheduler): Scheduler = {...}
223+
implicit def toJavaObserver[T](s: Observer[T]): rx.Observer[_ >: T] = {...}
224+
implicit def toScalaObserver[T](s: rx.Observer[_ >: T]): Observer[T] = {...}
225+
implicit def toJavaObservable[T](s: Observable[T]): rx.Observable[_ <: T] = {...}
226+
implicit def toScalaObservable[T](observable: rx.Observable[_ <: T]): Observable[T] = {...}
227+
}
228+
```

language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import scala.concurrent.duration._
2121
object Olympics {
2222
case class Medal(val year: Int, val games: String, val discipline: String, val medal: String, val athlete: String, val country: String)
2323

24-
def mountainBikeMedals: Observable[Medal] = Observable(
25-
Observable(
24+
def mountainBikeMedals: Observable[Medal] = Observable.items(
25+
Observable.items(
2626
Medal(1996, "Atlanta 1996", "cross-country men", "Gold", "Bart BRENTJENS", "Netherlands"),
2727
Medal(1996, "Atlanta 1996", "cross-country women", "Gold", "Paola PEZZO", "Italy"),
2828
Medal(1996, "Atlanta 1996", "cross-country men", "Silver", "Thomas FRISCHKNECHT", "Switzerland"),
@@ -31,7 +31,7 @@ object Olympics {
3131
Medal(1996, "Atlanta 1996", "cross-country women", "Bronze", "Susan DEMATTEI", "United States of America")
3232
),
3333
fourYearsEmpty,
34-
Observable(
34+
Observable.items(
3535
Medal(2000, "Sydney 2000", "cross-country women", "Gold", "Paola PEZZO", "Italy"),
3636
Medal(2000, "Sydney 2000", "cross-country women", "Silver", "Barbara BLATTER", "Switzerland"),
3737
Medal(2000, "Sydney 2000", "cross-country women", "Bronze", "Marga FULLANA", "Spain"),
@@ -40,7 +40,7 @@ object Olympics {
4040
Medal(2000, "Sydney 2000", "cross-country men", "Bronze", "Christoph SAUSER", "Switzerland")
4141
),
4242
fourYearsEmpty,
43-
Observable(
43+
Observable.items(
4444
Medal(2004, "Athens 2004", "cross-country men", "Gold", "Julien ABSALON", "France"),
4545
Medal(2004, "Athens 2004", "cross-country men", "Silver", "Jose Antonio HERMIDA RAMOS", "Spain"),
4646
Medal(2004, "Athens 2004", "cross-country men", "Bronze", "Bart BRENTJENS", "Netherlands"),
@@ -49,7 +49,7 @@ object Olympics {
4949
Medal(2004, "Athens 2004", "cross-country women", "Bronze", "Sabine SPITZ", "Germany")
5050
),
5151
fourYearsEmpty,
52-
Observable(
52+
Observable.items(
5353
Medal(2008, "Beijing 2008", "cross-country women", "Gold", "Sabine SPITZ", "Germany"),
5454
Medal(2008, "Beijing 2008", "cross-country women", "Silver", "Maja WLOSZCZOWSKA", "Poland"),
5555
Medal(2008, "Beijing 2008", "cross-country women", "Bronze", "Irina KALENTYEVA", "Russian Federation"),
@@ -58,7 +58,7 @@ object Olympics {
5858
Medal(2008, "Beijing 2008", "cross-country men", "Bronze", "Nino SCHURTER", "Switzerland")
5959
),
6060
fourYearsEmpty,
61-
Observable(
61+
Observable.items(
6262
Medal(2012, "London 2012", "cross-country men", "Gold", "Jaroslav KULHAVY", "Czech Republic"),
6363
Medal(2012, "London 2012", "cross-country men", "Silver", "Nino SCHURTER", "Switzerland"),
6464
Medal(2012, "London 2012", "cross-country men", "Bronze", "Marco Aurelio FONTANA", "Italy"),
@@ -80,7 +80,7 @@ object Olympics {
8080
// So we don't use this:
8181
// Observable.interval(fourYears).take(1).map(i => neverUsedDummyMedal).filter(m => false)
8282
// But we just return empty, which completes immediately
83-
Observable()
83+
Observable.empty[Medal]
8484
}
8585

8686
}

0 commit comments

Comments
 (0)