Skip to content
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

RxScala <-> RxJava converters #207

Merged
merged 8 commits into from
Sep 27, 2016
Merged

RxScala <-> RxJava converters #207

merged 8 commits into from
Sep 27, 2016

Conversation

rvanheest
Copy link
Contributor

Hi, based on my question on StackOverflow and the answer/invite by @samuelgruetter I got to work and created a JavaConverters class that is similar to the rx.lang.scala.JavaConversions class but does the conversion between RxJava and RxScala by using the asScala method (and asJava vise versa). The implementation is similar to the one in the Scala SDK (scala.collections.JavaConverters) and offers a way to do a more explicit implicit conversion.

Usage: although the use is quite obvious, I included an example of the intended use below.
Given a function that return a RxJava Observable (most likely coming from a Java based API), you transform it to a RxScala Observable using asScala:

import rx.lang.scala.JavaConverters._

object DemoJ2S extends App {

  def getJavaObservable: rx.Observable[Int] = rx.Observable.just(1, 2, 3)

  getJavaObservable.asScala.subscribe(i => println(i))
}

Similarly, you convert a RxScala Observable to an RxJava Observale using asJava:

object DemoS2J extends App {

    def getScalaObservable: Observable[Int] = Observable.just(1, 2, 3)

    getScalaObservable.asJava.subscribe(new Action1[Int] {
        override def call(i: Int): Unit = println(i)
    })
}

Besides Observable I included similar conversions for all types that were already in rx.lang.scala.JavaConversions (Observer, Scheduler, Worker, etc.).

Copy link
Collaborator

@samuelgruetter samuelgruetter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea, and it seems to be independent from the JavaConversions, so the two approaches could coexist.

I'd like to hear also @zsxwing's opinion.

Before this can be merged, there should be a test-and-example file for it, similar to RxScalaDemo.
The goal is not to test the functionality (that's trivial), but it's to test the signatures: That they can be used the in the intended way, without overloading problems, conflicting implicits, or implicit conversions not kicking in, etc... There's plenty of things which could go wrong here, especially at a later point when the library evolves. And moreover, the tests can also serve as a guide with usage examples.

I'd suggest that you create a new test file, in the same directory as the RxScalaDemo, where you show by some examples how the asScala/asJava extension methods can be used. And ideally, you'd also explain that there's a second approach in JavaConversions.


class AsJavaSubscription(s: Subscription) {
def asJavaSubscription: rx.Subscription = s.asJavaSubscription
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you use asJava for subscriptions as well?

* val javaObs = Observable.just(1, 2, 3).asJava
* val scalaObs = javaObs.asScala
* }}}
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation should be one space before the *

*/
object JavaConverters extends DecorateAsJava with DecorateAsScala

private[scala] trait Decorators {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(sorry for nitpicking) Code indentation in this project is 2 spaces, could you please adhere to this?

@rvanheest
Copy link
Contributor Author

in this response the RxJava variants of the types are prefixed with a j

Let me reply to your questions:

Could I add some examples similar to RxScalaDemo? - I actually had some examples but didn't commit them as they were not real tests. Did not see RxScalaDemo before. I added them 55b816b. This was also helpful for me, as I found some type level bugs with it!

spaces instead of tabs - fixed that

Why don't I use asJava for Subscription? - I did, but there are some type level problems with this, because of the relation between Subscription, Subscriber and Observer. For example, if you would have a Subscriber[T] somewhere (say in an Observable.apply), then calling asJava gives a jSubscriber[_ >: T] (welcome to covariance and contravariance!) but downcasting to jObserver[_ >: T] wouldn't work. To fix this the only solution is to give the extension method another name. Same thing (as I discovered in 55b816b) holds for jSubscription! You have to give it other names. As I have experienced that the most use of asJava and asScala is in jObservable to Observable and jObserver to Observer, I figured I use asJava and asScala for the Observer variant and asXSubscriber and asXSubscription for Subscriber and Subscription... I was not able to find any other solution. If you know a better one, feel free to suggest this.

Hope this clears up any of your questions.

val sSubscription: Subscription = jSubscription.asScalaSubscription
})
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent demo 😃

@samuelgruetter
Copy link
Collaborator

I tried to understand why we can't use asScala/asJava everywhere, so I quickly changed all asScalaXxx to asScala and also for Java, see #208.
(Aside: Please also have a look at commit eb6db12: Always give an explicit return type for public methods, otherwise bad things can happen, see the commit message).
If you try to compile it, a scala.reflect.internal.FatalError occurs, you can see it in the Travis CI log.
That is, we just found a bug in the Scala compiler 😬
Did you get this as well?
If you have time, could you please check if this also happens with the latest Scala compiler, and if yes, try to isolate and report it?
Finding a scalac bug with RxScala actually happened to me before, and the scalac maintainers could show me a simple work-around, so let's hope they'll know such a work-around for this one as well 😉

@rvanheest
Copy link
Contributor Author

rvanheest commented Sep 20, 2016

Yup, I got a similar error yesterday in the last example as well (I run Scala 2.11.8). Asked about it on https://gitter.im/scala/scala and got a response from @SethTisue (see below).

If I find time in the next couple of days, I'll have a look at this compiler bug. Let's keep in touch on this!

In the meantime I'll fix the Unit types you briefly mentioned...


https://files.gitter.im/scala/scala/6nER/blob

screen shot 2016-09-20 at 06 32 56

screen shot 2016-09-20 at 06 28 15

@zsxwing
Copy link
Member

zsxwing commented Sep 22, 2016

Since scala.collection.JavaConversions could cause a lot of problems implicitly and is deprecated in Scala 2.12.0, I suggest that we also deprecate rx.lang.scala.JavaConversions.

@rvanheest
Copy link
Contributor Author

@zsxwing I did not know that, but indeed read this in the scaladoc. Maybe this PR could provide a replacement for this. Although I really think that deprecating rx.lang.scala.JavaConversions should be done in a separate PR.

@samuelgruetter I tried to come up with a better solution than using separate keywords for Subscriber and Subscription, but kept walking into this same compiler error. Maybe you could propose a better solution if you know any?

Other than that, I consider this PR as a nice alternative for rx.lang.scala.JavaConversions.

@zsxwing
Copy link
Member

zsxwing commented Sep 23, 2016

Although I really think that deprecating rx.lang.scala.JavaConversions should be done in a separate PR.

Agreed.

@samuelgruetter
Copy link
Collaborator

I isolated the scalac bug:

import scala.language.implicitConversions

object Bug extends App {

  class JObserver[T]
  class JSubscriber[T] extends JObserver[T]

  class Foo

  implicit def convertSubscriber[T](s: JSubscriber[_ >: T]): Foo = ???

  implicit def convertObserver[T](s: JObserver[_ >: T]): Foo = ???

  val jSubscriber: JSubscriber[_ >: Int] = ???

  val foo: Foo = jSubscriber // compiler crashes

}

I think it's SI-5559, and it seems that there is no simple work-around...

So I guess we have to live with the more verbose asScalaXxx methods, and I'd say LGTM, ready to merge. Thanks @rvanheest!

@rvanheest
Copy link
Contributor Author

My pleasure, mate! You guys are doing great stuff and I'm happy to contribute.

@zsxwing zsxwing merged commit a5aa49e into ReactiveX:0.x Sep 27, 2016
@rvanheest rvanheest deleted the JavaConverters branch September 27, 2016 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants