- PVSM.RU - https://www.pvsm.ru -
Многие сегодня любят реактивное программирование. В нём масса плюсов: и отсутствие так называемого "callback hell [1]", и встроенный механизм обработки ошибок, и функциональный стиль программирования, который уменьшает вероятность багов. Значительно проще писать многопоточный код и легче управлять потоками данных (объединять, разделять и преобразовывать).
Для многих языков программирования существует своя реактивная библиотека: RxJava для JVM, RxJS — для JavaScript, RxSwift — для iOS, Rx.NET и т. д.
Но что мы имеем для Kotlin? Было бы логично предположить, что RxKotlin. И, действительно, такая библиотека существует, но это всего лишь набор расширений (extensions) для RxJava2, так называемый «сахар».
А в идеале хотелось бы иметь решение, соответствующее следующим критериям:
Observable<String?>
);
Observable<String>
к Observable<CharSequence>
.
Мы в Badoo решили не ждать у моря погоды и сделали такую библиотеку. Как вы уже могли догадаться, назвали мы её Reaktive и выложили на GitHub [5].
В этой статье мы подробнее рассмотрим ожидания от реактивного программирования на Kotlin и увидим, насколько им соответствуют возможности Reaktive.
Первое естественное преимущество наиболее важно. В настоящее время наши iOS-, Android- и Mobile Web-команды существуют отдельно. Требования общие, дизайн одинаковый, но свою работу каждая команда делает сама по себе.
Kotlin позволяет писать мультиплатформенный код, но про реактивное программирование придётся забыть. А хотелось бы иметь возможность писать общие библиотеки с использованием реактивного программирования и распространять их внутри компании или выкладывать на GitHub. Потенциально такой подход может существенно сократить время разработки и уменьшить общее количество кода.
Это скорее про недостаток Java и RxJava2. Если вкратце, то null использовать нельзя. Давайте попробуем разобраться почему. Взгляните на этот Java-интерфейс:
public interface UserDataSource {
Single<User> load();
}
Может ли результат быть null? Чтобы исключить неясности, в RxJava2 запрещено использовать null. А если всё же надо, то есть Maybe и Optional. Но в Kotlin таких проблем нет. Можно сказать, что Single и Single<User?> — это разные типы, и все проблемы всплывают ещё на этапе компиляции.
Это отличительная особенность Kotlin, то, чего очень не хватает в Java. Подробно об этом можно почитать в руководстве [4]. Приведу лишь пару интересных примеров того, какие проблемы возникают при использовании RxJava в Kotlin.
Ковариантность:
fun bar(source: Observable<CharSequence>) {
}
fun foo(source: Observable<String>) {
bar(source) // Ошибка компиляции
}
Поскольку Observable
— это интерфейс Java, то такой код не скомпилируется. Это потому что generic-типы в Java инвариантны. Можно, конечно, использовать out, но тогда применение операторов вроде scan опять приведёт к ошибке компиляции:
fun bar(source: Observable<out CharSequence>) {
source.scan { a, b -> "$a,$b" } // Ошибка компиляции
}
fun foo(source: Observable<String>) {
bar(source)
}
Оператор scan отличается тем, что его generic тип «T» является сразу и входным, и выходным. Если бы Observable был интерфейсом Kotlin, то можно было бы его тип T обозначить как out и это решило бы проблему:
interface Observable<out T> {
…
}
А вот пример с контравариантностью:
fun bar(consumer: Consumer<String>) {
}
fun foo(consumer: Consumer<CharSequence>) {
bar(consumer) // Ошибка компиляции
}
По той же причине, что и в предыдущем примере (generic-типы в Java инвариантны), этот пример не компилируется. Добавление in решит проблему, но опять же не на сто процентов:
fun bar(consumer: Consumer<in String>) {
if (consumer is Subject) {
val value: String = consumer.value // Ошибка компиляции
}
}
fun foo(consumer: Consumer<CharSequence>) {
bar(consumer)
}
interface Subject<T> : Consumer<T> {
val value: T
}
Ну и по традиции в Kotlin эта проблема решается использованием in в интерфейсе:
interface Consumer<in T> {
fun accept(value: T)
}
Таким образом, вариантность и контравариантность generic типов являются третьим естественным преимуществом библиотеки Reaktive.
Переходим к главному — описанию библиотеки Reaktive.
Вот несколько её особенностей:
ObservableSource
в Reaktive называется просто Observable
, а все операторы — это extension-функции, расположенные в отдельных файлах. Нет God-классов по 15 000 строк. Это даёт возможность легко наращивать функциональность, не внося изменения в имеющиеся интерфейсы и классы.
subscribeOn
и observeOn
).
Хотелось бы чуть больше рассказать о преимуществах, которые мы получили за счёт того, что библиотеки на Kotlin.
observableOf<String>(null) // ошибка компиляции
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String?> = o1 // ошибка компиляции, несоответствие типов
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String?> = o1.notNull() // ошибки нет, значения null отфильтрованы
val o1: Observable<String?> = observableOf("Hello")
val o2: Observable<String?> = o1 // ошибки нет
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String?> = observableOf("Hello")
val o3: Observable<String?> = merge(o1, o2) // ошибки нет
val o4: Observable<String?> = merge(o1, o2) // ошибка компиляции, несоответствие типов
Вариантность — тоже большое преимущество. Например, в интерфейсе Observable
тип T объявлен как out
, что даёт возможность написать примерно следующее:
fun foo() {
val source: Observable<String> = observableOf("Hello")
bar(source) // ошибки нет
}
fun bar(source: Observable<CharSequence>) {
}
Так выглядит библиотека на сегодняшний день:
Observable
, Maybe
, Single
и Completable
;
Что у нас в планах на ближайшее будущее:
Попробовать библиотеку можно уже сейчас, всё необходимое вы найдёте на GitHub [5]. Делитесь опытом использования и задавайте вопросы. Будем благодарны за любой фидбек.
Автор: Аркадий Иванов
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/314300
Ссылки в тексте:
[1] callback hell: https://en.wiktionary.org/wiki/callback_hell
[2] Null safety: https://kotlinlang.org/docs/reference/null-safety.html
[3] ошибки на миллиард долларов: https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions
[4] ковариантность и контравариантность: https://kotlinlang.org/docs/reference/generics.html
[5] GitHub: https://github.com/badoo/Reaktive
[6] ReactiveX: http://reactivex.io/
[7] Источник: https://habr.com/ru/post/447424/?utm_source=habrahabr&utm_medium=rss&utm_campaign=447424
Нажмите здесь для печати.