2014-01-03 7 views
4

Я пытаюсь обернуть rxjava timeoutmethod, чтобы сделать его доступным for scala.Границы типа Scala и общий набор Java

Подобно many other methods Я попытался это:

def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = { 
    val otherJava: rx.Observable[_ <: U] = other.asJavaObservable 
    val thisJava: rx.Observable[_ <: U] = this.asJavaObservable 
    toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava)) 
} 

Но я получаю следующее сообщение об ошибке:

Observable.scala:1631: error: overloaded method value timeout with alternatives: 
($1: Long,x$2: java.util.concurrent.TimeUnit,x$3: rx.Scheduler)rx.Observable[_$85] <and> 
($1: Long,x$2: java.util.concurrent.TimeUnit,x$3: rx.Observable[_ <: _$85])rx.Observable[_$85] 
cannot be applied to (Long, scala.concurrent.duration.TimeUnit, rx.Observable[_$84]) 
    toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava)) 

оригинальный метод Java:

public Observable<T> timeout(long timeout, TimeUnit timeUnit, Observable<? extends T> other) { 
    return create(OperationTimeout.timeout(this, timeout, timeUnit, other)); 
} 

Я не очень не знакомы ни с Java, ни с Scala (и всеми ограничениями типа), но насколько я понимаю: оба otherJava, а также thisJava имеет тип rx.Observable[U], так почему бы им не выйти?

ответ

3

Худ, вы наступаете на проблемы дисперсии Java-дженериков, используемых в Scala. Пойдем шаг за шагом.


Давайте посмотрим на вашей реализации:

// does not compile (with your original error) 
def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = { 
    val otherJava: rx.Observable[_ <: U] = other.asJavaObservable 
    val thisJava: rx.Observable[_ <: U] = this.asJavaObservable 
    toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava)) 
} 

Чтобы понять, почему это не будет работать, давайте назовем A безымянного типа в объявлении thisJava (A <: U таким образом, что thisJava является rx.Observable[A]) , Метод timeoutthisJava: rx.Observable[A] ожидает параметр типа rx.Observable[_ <: A], и вы даете ему один из типов rx.Observable[_ <: U]: компилятор не имеет способа узнать, как эти два типа связаны. Они могут быть не связаны вообще!

С другой стороны, если A были U, то thisJava бы rx.Observable[U], и метод его timeout бы ожидать rx.Observable[_ <: U], что просто случается быть тип otherJava. Давайте попробуем:

// still does not compile, sadly 
def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = { 
    val otherJava: rx.Observable[_ <: U] = other.asJavaObservable 
    val thisJava: rx.Observable[U] = this.asJavaObservable // variance error 
    toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava)) 
} 

В прекрасном мире вышеуказанное будет работать. Однако, java rx.Observable не определен как ковариантный, так как в java нет аннотаций вариации на сайте определения. Поэтому Скала считает, что она инвариантна.

Следовательно, насколько Scala это обеспечить, rx.Observable[_ <: U] является не в rx.Observable[U], и, к сожалению this.asJavaObservable возвращает rx.Observable[_ <: U].


Но мы знаем, [*], что rx.Observable<T> должен быть ковариантны, поэтому мы можем просто слепо разбрасывать:

// this compiles and *should* work 
def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = { 
    val otherJava: rx.Observable[_ <: U] = other.asJavaObservable 
    val thisJava = this.asJavaObservable.asInstanceOf[rx.Observable[U]] 
    toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava)) 
} 

Мораль этой истории является то, что смешивание дисперсии Scala и дисперсия в Java всегда будет стоимость вы несколько бросили здесь и там, о чем нужно тщательно подумать.

Кроме того, изготовление asJavaObservable возвращает rx.Observable[T] вместо _ <: T, что сделало бы все это проще, но, возможно, есть веские причины, почему это не так ...

[*] больше похоже на «но я подозреваю, что»

+0

Но тогда я потерял гибкость передачи супертипа. Наблюдаемый как «другой». Я думаю, что для большей гибкости библиотека должна допускать другой тип 'U'. Также обратите внимание, что '' действительно переведен на '[_ <: T]' как [отмеченный в истории scala] (http://www.scala-lang.org/download/changelog.html#java_generics) – ClojureMostly

+0

@Aralo о, ты совершенно прав. Фактически, он даже не компилируется без некоторого параметра типа 'U', потому что ковариантный тип не может появиться (в' other') в контравариантной позиции. Я удалил свой первый пункт и обновил код. – gourlaysama

+0

Отличное объяснение. Я следую вашим аргументам и работает (см. [Мой запрос на тягу] (https://github.com/Netflix/RxJava/pull/720)). Однако, это все еще запутывает, так как я думал, что они одного типа. Давайте проверим его с помощью «List», который также является ковариантным: http://paste.ubuntu.com/6692064/ Вот почему я думал, что они оба одинаковы/типа. Все еще запутано. – ClojureMostly

2

[это должно идти в комментариях ответ @ gourlaysama, но я не хватает репутации комментировать]

@Aralo утверждение " MyType[_ <: T] - это то же самое, что и MyType[T] "действует только в том случае, если компилятор знает, что MyType является ковариантным. Это имеет место с List, потому что он определен как List[+A], но это не тот случай с rx.Observable, потому что это тип Java, поэтому его параметры типа не могут иметь аннотации дисперсии, поэтому компилятор не может знать, что он предназначен для ковариантности.

@gourlaysama делает asJavaObservable Вернуть rx.Observable[T] вместо _ <: T это не решения, так как тип rx.lang.scala.Observable[T] означает «наблюдаемый из T или чего-то, который является подтипом T», и это описание точно соответствует типу rx.Observable[_ <: T] (что совпадает с rx.Observable<? extends T>).

Причина, почему мы должны бросить в Scala является то, что подпись timeout в Java «неправильно»: Строго говоря, это делает Java Наблюдаемым инварианта, поскольку T появляется в контравариантном положении. «Правильным» способом было бы использовать другой параметр типа U, нижняя граница которого равна T, как и в Scala, но Java не поддерживает нижние границы, поэтому лучшим решением является сохранение «неправильного» решения. Эта проблема возникает также с reduce (см. this comment), onErrorReturn и несколькими другими операторами.

В общем, все эти операторы, которые должны иметь ограниченный снизу параметр типа (но не) могут быть использованы только на Observable<T>, но не на Observable<? extends T> (довольно серьезное неудобство для пользователей Java), и, таким образом, они требуют заливки в адаптере Scala.

Смежные вопросы