Статьи

Cyclops-реакция организовывает кембрийский взрыв библиотек Java 8

Что такое циклоп-реакция?

Появление лямбда-выражений и методов по умолчанию в Java 8 ознаменовало самые большие структурные изменения в языке Java за последние десять лет. Вдобавок к этому были добавлены некоторые новые классные API-интерфейсы, такие как Stream, Optional, CompletableFuture — наконец, разработчики Java могли Stream, Optional, CompletableFuture код в более функциональном стиле. Хотя это было очень кстати, для многих улучшений не хватило.

Stream, Optional, CompletableFuture имеют общую абстрактную структуру и подчиняются одним и тем же правилам. Тем не менее, API не согласуются с общими именами методов, не говоря уже о предоставлении общего интерфейса. Например, Stream#map / Optional#map становится CompletableFuture#thenApply . Кроме того, функциональность, добавленная в Stream & Optional вообще отсутствует в коллекциях. Где находится List#map ?

Реализация JDK Stream работает хорошо, полностью ленива и хорошо разработана для расширения, но предоставляет только ограниченное подмножество потенциальных операторов (возможно, ограниченное фокусом на параллелизме данных). В пустые ступенчатые библиотеки, такие как jOOλ с его последовательным расширением Stream (называемым Seq ). Seq добавляет много дополнительных потоковых операторов. В jOOλ обычно добавлено множество отсутствующих функциональных возможностей, таких как Tuples.

Основная цель циклоп-реакции , а также добавления оригинальных функций, таких как FutureStreams , заключается в предоставлении механизма для объединения как JDK API, так и сторонних функциональных библиотек. После запуска Java 8 появился кембрийский взрыв классных библиотек. Такие библиотеки, как Javaslang & Project Reactor . Циклоп-реакция делает это в первую очередь, расширяя JDK и используя другие библиотеки, такие как jOOλ , pCollections & Agrona . Эти библиотеки, в свою очередь, также расширяют интерфейсы JDK, где это возможно, для добавления функций, таких как постоянные коллекции, и ожидания без очереди для многих производителей.

Помимо повторного использования и расширения интерфейсов JDK, наши цели заключались в том, чтобы облегчить разработчикам интеграцию с внешними библиотеками, используя сторонние стандарты, такие как API реактивных потоков, и создавая собственные абстракции там, где не существует установленного стандарта. В настоящее время мы специализируемся на интеграции с библиотеками Google: Guava, RxJava, Functional Java, Project Reactor и Javaslang . Мы создали абстракции для обтекания типов, таких как Stream, Optional & CompletableFuture — там, где ранее не было или не было интерфейса. Мы выбрали эти цели, потому что мы используем циклопическое реагирование в производственной среде в архитектуре микросервисов, и нам важно использовать правильную технологию для решения проблемы и обеспечить ее плавную интеграцию с остальной частью нашей базы кода.

Циклоп-реакция — довольно большой многофункциональный проект, кроме того, он имеет ряд модулей интеграции . В статье ниже я расскажу о некоторых доступных функциях с конкретной целью показать, как циклопическая реакция помогает объединить точки в JDK и войти в дивный новый мир динамичного сообщества Java 8 с открытым исходным кодом.

Расширение JDK

циклоп-реакция расширяет API JDK, где это возможно. Например, ReactiveSeq добавляет функциональность для обработки ошибок, асинхронной обработки и многое другое расширяет как JDK Stream, так и Seq в jOOλ. Циклоп-реагирование Расширения коллекции, вместо создания новых реализаций коллекции, реализуют и расширяют соответствующие интерфейсы JDK. cyclops-реагировать LazyFutureStream в свою очередь, расширяет ReactiveSeq и позволяет агрегировать операции над потоками фьючерсов, как если бы это был простой поток (это оказывается очень полезным для асинхронной и производительной обработки большого количества типичных операций ввода-вывода Java).

ListX расширяет List , но добавляет операторы, которые выполняются с нетерпением

1
2
ListX<Integer> tenTimes = ListX.of(1,2,3,4)
                               .map(i->i*10);

Циклоп-реакция добавляет множество операторов для пользователей, чтобы исследовать. Мы можем, например, применять функции для нескольких коллекций одновременно

API реактивных потоков действует как естественный мост между производителями (издателями) данных и потребителями (подписчиками). Все типы данных Циклоп-Реакция реализуют интерфейс Publisher из реактивных потоков, также предоставляются реализации Subscriber которые могут конвертировать в любой тип Циклоп-Реакция. Это упрощает прямую интеграцию с другими библиотеками на основе реактивных потоков, такими как Project Reactor.

Например, мы можем лениво заполнять Reactor Flux от любого издателя циклопов, такого как SortedSetX , или заполнять тип реагирования циклопов из типа Reactor.

1
2
3
4
5
6
Flux<Integer> stream = Flux.from(
  SortedSetX.of(1,2,3,4,5,6,7,8));
//Flux[1,2,3,4,5,6,7,8]
 
ListX<Character> list = ListX.fromPublisher(
  Flux.just("a","b","c"));

Типы Reactor Flux и Mono могут работать напрямую с циклопами-реакциями For понимания (каждая поддерживаемая библиотека также имеет свой собственный набор собственных классов For понимания в своем модуле интеграции).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// import static com.aol.cyclops.control.For.*;
         
Publishers.each2(
  Flux.just(1,2,3),
  i -> ReactiveSeq.range(i,5),Tuple::tuple).printOut();
         
/*
(1, 1)
(1, 2)
(1, 3)
(1, 4)
(2, 2)
(2, 3)
(2, 4)
(3, 3)
(3, 4)
*/

For понимания — это способ управления вложенными итерациями над типами с помощью методов flatMap и map путем каскадного обращения к соответствующим методам. В циклоп-реакции вложенные операторы могут получить доступ к элементам предыдущих операторов, поэтому For понимания может быть очень полезным способом управления поведением существующих. Например, чтобы гарантировать, что вызовы существующих методов findId и loadData, которые могут возвращать нулевые значения, и будут выбрасывать NPE, если они снабжены нулевым параметром, мы можем использовать For compception, который безопасно выполнит loadData только при возврате Optional со значением. от findId ()

1
2
3
4
List<Data> data =
For.optional(findId())
   .optional(this::loadData);
//loadData is only called if findId() returns a value

Аналогично, такой тип, как Try, можно использовать для обработки исключительных результатов из findId или loadData, Futures можно использовать для асинхронного выполнения цепочечных методов и так далее.

Создание межбиблиотечных абстракций

Java 8 представила Monads для Java ( Stream, Optional, CompletableFuture ), но не предоставила общего интерфейса, который помог бы использовать повторно, фактически имена методов, используемые в CompletableFuture значительно отличаются от имен, используемых в Optional & Stream для той же функции. Таким образом, map стала тогда thenApply и flatMap thenCompose . В мире Java 8 монады становятся все более распространенной моделью, но зачастую нет возможности абстрагироваться от них. В циклоп-реакции вместо того, чтобы пытаться определить интерфейс для представления монад, мы создали набор интерфейсов-оболочек и несколько пользовательских адаптеров для адаптации различных экземпляров основных библиотек функционального стиля для Java 8 к этим оболочкам. AnyM расширяют AnyM (сокращение от Any Monad), и есть два подчиненных интерфейса — AnyMValue представляющий любой монадический тип, который разрешается в одно значение (например, Optional или CompletableFuture ), или AnyMSeq который в конечном итоге разрешается в последовательность значений (например, Stream). или список). Оболочки расширения cyclops предоставляют механизм для переноса типов из RxJava, Guava, Reactor, FunctionalJava и Javaslang.

1
2
3
4
5
6
7
//We can wrap any type from Reactor, RxJava,
//FunctionalJava, Javaslang, Guava
AnyMSeq<Integer> wrapped =
  Fj.list(List.list(1,2,3,4,5));
 
//And manipulate it
AnyMSeq<Integer> timesTen = wrapped.map(i->i*10);

Циклоп-реакция предоставляет общий набор интерфейсов, от которых наследуются эти оболочки (и другие типы Циклоп-реагирования), позволяя разработчикам писать более универсальный повторно используемый код. AnyM расширяет возможности AnyM реактивных потоков, то есть вы можете сделать любой тип Javaslang, Guava, FunctionalJava или RxJava издателем реактивных потоков с помощью cyclops-реагировать.

1
2
3
4
5
6
7
8
9
AnyMSeq<Integer> wrapped =
  Javaslang.traversable(List.of(1,2,3,4,5));
 
//The wrapped type is a reactive-streams publisher
Flux<Integer> fromJavaslang = Flux.from(wrapped);
 
wrapped.forEachWithError(
  System.out::println,
  System.out::err);

Кроме того, реактивная функциональность от циклопа-реактива предоставляется непосредственно на типах AnyM. Это означает, что мы можем, например, запланировать передачу данных из потока Javaslang или FunctionalJava — или выполнить операцию уменьшения медленно или асинхронно.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
AnyMSeq<Integer> wrapped =
  Javaslang.traversable(Stream.of(1,2,3,4,5));
 
CompletableFuture<Integer> asyncResult =
  wrapped.futureOperations(Executors.newFixedThreadPool(1))
         .reduce(50, (acc, next) -> acc + next);
//CompletableFuture[1550]
 
AnyMSeq<Integer> wrapped =
  FJ.list(list.list(1,2,3,4,5));
 
Eval<Integer> lazyResult =
  wrapped.map(i -> i * 10)
         .lazyOperations()
         .reduce(50, (acc,next) -> acc + next);
//Eval[15500]
 
HotStream<Integer> emitting = wrapped.schedule(
  "0 * * * * ?",
  Executors.newScheduledThreadPool(1));
 
emitting.connect()
        .debounce(1,TimeUnit.DAYS)
        .forEachWithError(
           this::logSuccess,
           this::logFailure);

Существует много возможностей для изучения как циклопов-реакции, так и новой более широкой экосистемы Java 8, и, надеюсь, у вас получится увлекательное приключение, в котором вы сможете поиграть, изучить и расширить границы Java 8!