RxSealedUnions


Source link: https://github.com/pakoito/RxSealedUnions

RxSealedUnions

This repository is a RxJava backport of Java 8's JavaSealedUnions library.

For the RxJava 2.X version please go to RxSealedUnions2.

EXAMPLES

You can see RxSealedUnions used through the Functional Android Reference app released as a companion example for the talk on Fully Reactive Apps at Droidcon UK 2016.

DISTRIBUTION

Add as a dependency to your build.gradle

 repositories {

...

maven {
 url "https://jitpack.io" 
}

...
  
}

dependencies {

...

compile 'com.github.pakoito:RxSealedUnions:1.1.0'

...
  
}

or to your pom.xml

 <repositories>

<repository>

 <id>jitpack.io</id>

 <url>https://jitpack.io</url>

</repository>
  </repositories>

<dependency>

<groupId>com.github.pakoito</groupId>

<artifactId>RxSealedUnions</artifactId>

<version>1.1.0</version>
  </dependency>

ACKNOWLEDGEMENTS

This library was heavily inspired by RxEither and the wonderful Domain Driven Design (DDD) talk by Scott Wlaschin. Another similar talk with the full Tennis kata we'll use as an example below is Types + Properties = Software by Mark Seemann.

RATIONALE

RxSealedUnions brings unions into idiomatic Java 6 using Reactive Extensions to allow for better domain modelling. It can also help representing sealed classes, but that is not the main focus. Chaining operations and monadic composition using RxSealedUnions is also outside the scope of the library, but any union can be lifted to Observables as shown by RxEither.

Java's type system is considered not very powerful although it contains most OOP niceties. Some of the most known absences are tagged unions and sealed classes. Sealed classes are available in languages like Kotlin, or C#. Tagged unions are common on Swift and Rust.

The most frequent approach to solve this modelling problem in Java is having a base class or interface IMyContract and implementing several of IChildContractPlusExtras for public scopes. Another alternative is having a public abstract class that is inherited by a small set of package-only classes. The problem with the first approach is the possibility of breaking encapsulation and being able to implement the interface by a 3rd party outside the desired outcomes. The second approach hides the implementations for you, which requires the use of runtime tools like instanceof to handle. Both cases have one common problem: they only allow association of classes that are of the same type, or in the same hierarchy.

The intent of this library is to create a set of classes that can host one element of one to several arbitrary complex types. Historically other libraries like Guava or RxJava have provided chainable types to solve this issue:

  • The base case is Result<T> (also known as Optional), which is the union of two types: Some<T> and None.

  • The next case is Either<A, B> or Try<T, Exception>, which respectively wrap the union of two arbitrary types Left<L> and Right<R>, or Success<T> and Failure<Exception>.

For a higher number of parameters no abstraction is usually provided, and it's when other languages change to explicit union types. As Java does not have unions on the language, we have to continue abstracting away with 3 types (Left, Middle and Right), 4 types, 5 types...etc.

We're calling them Union1<T> for Result/ Optional, Union2<L, R> for Either/ Try, Union3<L, M, R>...up to UnionN, which for this library would be Union9<A, B, C, D, E, F, G, H, I>. A special case for Union0<A> is given for cases where you simply require to box a type for design purposes, and future improvements.

I heavily recommend watching the DDD talk linked above first to see what solutions this library provides compared to chainable types. Unions are used for intra-layer modelling, chainables are more fit for inter-layer communication.

INTERFACES

Now that we understand what the abstraction has to provide, we have to define a public API:

What defines a union?

It's a type that allows storage of a single element that can be from any of 2-N different types. It's container and implementation agnostic. The only requirement is to have ways to retrieve the data inside. To be properly composable it requires using interface composition rather than abstract inheritance.

What belongs in the interface?

It needs to be able to dereference the types to obtain a single, unequivocal, result. It should avoid extra operations, not throw exceptions, and not be able to represent error states.

How is this done in other languages or frameworks?

  • Nested ifs:
if (union.isOne()) {

  One element = union.getOne();

  /* do something with one */  
}
 else if (union.isTwo()) {

  Two element = union.getTwo();

  /* do something with two */  
}
 else...

... and so and so until 9. Problem? The api can be dereferenced using getXXX() without calling any of the isXXX() methods, leading to exceptions and unexpected states. It adds one unneeded extra operation.

  • Subtyping:
MyElement element = createElement();
 if (element instanceof One) {

  One one = (One)element;
  /* do something with one */ 
}
 else if (element instanceof Two) {

  Two two = (Two)element;
  /* do something with two */ 
}
 else...

It suffers from the same shortcomings as nested ifs: it requires programmer discipline to remember and check before casting, plus it leans to the same errors. The only change is that it now requires two operations: instanceof and a cast.

  • Pattern matching: not available in Java. But the intent of a pattern matcher is double: either continue to another piece of code, or return a single result element. This ties directly to the next two points.

  • Continuations: part of Inversion of Control, it's a principle where a function decides what to do with the execution flow after it's done. In this case you provide one method per type in the union indicating how the operation has to continue after dereferencing. It branches execution synchronously into the continuations without having to check types externally. It does not allow representing incorrect states, dereferencing unavailable types, or any other causes of Exceptions save for NPEs.

  • Joining: provide a function per element in the union that maps them back into a single common type that the current execution flow knows how to use. The mapping is applied synchronously and the flow continues on the same method. The caller is responsible of assuring the mapping operation is correct, or have a fallback case.

For the library I have chosen continuations and joining as the default methods in the interface.

NOTE: you should never ever require or implement getXXX() as a way of returning a type inside the union. It defeats the purpose of the library. Direct dereference is error-prone, tends to be abused by programmers, and has been cited as a mistake when creating Optional-like libraries.

Final implementation of Union2

public interface Union2<First, Second> {

void continued(Action1<First> continuationFirst, Action1<Second> continuationSecond);

<R> R join(Func1<First, R> mapFirst, Func1<Second, R> mapSecond);
 
}

And one example usage:

Union2<User, Team> information = serverRequester.loggedAccountInformation();
  // Get a single piece of information from either List<Purchase> totalPurchases = information.join(user -> user.getPurchases(), team -> team.getAccountManager().getPurchases());
  // Or you can finish the current method and continue somewhere else depending on the type information.continued(UserPageTemplater::start(), TeamPageTemplater::start());

Creation

Part of creating a union is that the union itself is a new type and has to be represented too. For this case it's been included one Factory interface per UnionN that can be extended and required to create each one of the elements in the union:

public interface Factory<Left, Right> {

Union2<Left, Right> first(Left first);

Union2<Left, Right> second(Right second);
  
}

USAGE

Generic unions

This set of classes are provided by the library to wrap any class regardless of its type. They come in flavours from Union0 to Union9. GenericUnions is a class with factories for all the union types. Factories can be provided by calling one of nulletFactory(), singletFactory(), doubletFactory(), tripletFactory(), quartetFactory(), quintetFactory(), sextetFactory(), septetFactory(), octetFactory() and nonetFactory().

public class LoggedInAccount {

  public final String id;

public long timestamp;

public final Union4<User, Group, Administrator, Guest> account;

public LoggedInAccount(String id, long timestamp, Union4<User, Group, Administrator, Guest> account){

this.id = id;

this.timestamp = timestamp;

this.account = account;
  
}
 
}
  public LoggedInAccount login(String id){

  Union4.Factory<User, Group, Administrator, Guest> quartetFactory = GenericUnions.quartetFactory();

  Union4<User, Group, Administrator, Guest> user =

 database.getAccount(id)

  ? quartetFactory.third(database.requestAdmin(id))

  : quartetFactory.first(database.requestUser(id));

  LoggedInAccount account = new LoggedInAcctount(id, System.currentTimeMillis(), user);

  return account; 
}

Typed wrappers

In case you want your unions to be driven by your domain you have to create your own classes implementing the base interfaces. There are several recommended approaches:

Holder class with generic union

A domain class giving a more explicit naming and access to its methods and content.

REMINDER: Implement getXXX() as a way of returning a type inside the union defeats the purpose of unions.

public class Salute {

private static final Either.Factory<Dog, Neighbour> FACTORY = GenericUnions.eitherFactory();

public static Salute dog(String name, int paws) {

return new Salute(FACTORY.first(new Dog(name, paws)));

  
}

public static Salute neighbour(String name, String favouriteFood, boolean isLiked) {

return new Salute(FACTORY.second(new Neighbour(name, favouriteFood, isLiked)));

  
}

private final Union2<Dog, Neighbour> either;

Salute(Union2<Dog, Neighbour> either) {

this.either = either;
  
}

public void openDoor(Action1<Dog> continueDog, Action1<Neighbour> continueNeighbour) {

return either.continued(continueDog, continueNeighbour);

  
}

public String rememberSalute(Func1<Dog, String> mapDog, Func1<Neighbour, String> mapNeighbour) {

return either.join(mapDog, mapNeighbour);

  
}
 
}

// Example String salute = getSalute().rememberSalute(dog -> "Who's a good dog?", neighbour-> neighbour.isLiked? "Good morning, " + neighbour.name + "!" : "*grunt*");
 getSalute().openDoor(dogSaluter::salute(), neighbourSaluter::salute());

Subclassing

This ties up to the inheritance approach, except it's sealed and explicit. It can be done by both abstract classes or interfaces.

As a personal rule I would avoid any inherited methods, overloading, or overriding in any of the child classes. Watch the DDD talk in the Acknowledgements section to better understand the use of union types as plain data.

The example below breaks this rule by adding a new method valid().

public abstract class PaymentType implements Union3<CardPayment, PayPalPayment, BankTransferPayment> {

public abstract boolean valid();

public static PaymentType card(String cardNo, String ccv) {

return new CardPayment(cardNo, ccv);

  
}

public static PaymentType paypal(String paypalNo, String password) {

return new PayPalPayment(paypalNo, password);

  
}

public static PaymentType bankTransfer(String accNo) {

return new BankTransferPayment(accNo);

  
}
 
}
  class CardPayment extends PaymentType {

private final String cardNo;

private final String ccv;

CardPayment(String cardNo, String ccv) {

this.cardNo = cardNo;

this.ccv = ccv;
  
}

 public boolean valid() {

return /* some logic here */
  
}

public void continued(Action1<CardPayment> continuationLeft, Action1<PayPalPayment> continuationMiddle, Action1<BankTransferPayment> continuationRight) {

continuationLeft.call(value);

  
}

public <T> T join(Func1<CardPayment, T> mapLeft, Func1<PayPalPayment, T> mapMiddle, Func1<BankTransferPayment, T> mapRight) {

return mapLeft.call(value);

  
}
 
}
  class PayPalPayment extends PaymentType {

private final String user;

private final String pwd;

CardPayment(String user, String pwd) {

this.user = user;

this.pwd = pwd;
  
}

 public boolean valid() {

return /* some logic here */
  
}

public void continued(Action1<CardPayment> continuationLeft, Action1<PayPalPayment> continuationMiddle, Action1<BankTransferPayment> continuationRight) {

continuationMiddle.call(value);

  
}

public <T> T join(Func1<CardPayment, T> mapLeft, Func1<PayPalPayment, T> mapMiddle, Func1<BankTransferPayment, T> mapRight) {

return mapMiddle.call(value);

  
}
 
}

class BankTransferPayment extends PaymentType {

private final String accountNo;

CardPayment(String accountNo){

this.accountNo = accountNo;
  
}

 public boolean valid() {

return /* some logic here */
  
}

public void continued(Action1<CardPayment> continuationLeft, Action1<PayPalPayment> continuationMiddle, Action1<BankTransferPayment> continuationRight) {

continuationRight.call(value);

  
}

public <T> T join(Func1<CardPayment, T> mapLeft, Func1<PayPalPayment, T> mapMiddle, Func1<BankTransferPayment, T> mapRight) {

return mapRight.call(value);

  
}
 
}
  // Example  PaymentType payment = getUserPayment();
 if (payment.valid()) {

  payment.continued(paymentService::byCard(), paymentService::withPayPal(), paymentService::viaBankTransfer()) 
}

DDD

The last approach is the recommended to make the most out of the principles described across this document, using types rather than inheritance or fields.

A complete version of the Tennis kata can be found in TennisGame.java along with usage tests at TennisGameTest.java

public abstract class Score {

Union4<Points, Advantage, Deuce, Game> getScore();
 
}
  public final class Points extends Pair<PlayerPoints, PlayerPoints> {
 
}
  public abstract class PlayerPoints {

Union4<Zero, Fifteen, Thirty, Forty> getPlayerPoints();
 
}
  public abstract class Advantage extends Player {
 
}
  public final class Deuce {
 
}
  public abstract class Game extends Player {
 
}
  public abstract class Player {

Union2<PlayerOne, PlayerTwo> getPlayer();
 
}

License

Copyright (c) pakoito 2016

The Apache Software License, Version 2.0

See LICENSE.md

Resources

An application that helps you learn and better understand ReactiveX on Android.

Codeview is an Android library that lets you to preview code in WebView very simple with highlighting.

This Circular Progress bar is a designed and made for music players that need beautiful music progress bar.

A simple android gradle plugin to smart split the specified classes to second dex with patterns.

A reactive cache for Android and Java which honors the Observable chain.

ChipLayout is an layout that allows to create chips from text you typed with possibility to show drop down.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes