Introduction: The last article introduced the origin of the Observable class. But operators are another big advantage of RxJava. In this article, I’ll introduce you to the concepts behind operators. (Reading this article may cause severe physical discomfort, and may even cause you to read operators that you know before but don’t. It’s not even going to help you develop an Android App, so read this article with care.)

Before we get to operators, we need to understand a few concepts: Monad and functional programming. I will cover each of them here, but I won’t go into too much detail. One article certainly can’t cover these two huge concepts in detail, and I don’t even understand them thoroughly myself, but that doesn’t stop us from understanding RxJava operators.

Functional programming

Let’s start with functional programming, and the idea of functional programming is very simple. That’s programming with functions. In other words, programming is done using mathematical functions. A function is a mapping between two sets. We often use the form f:x -> y to represent the function f as a mapping from x to y. To use the Kotlin language that we’re familiar with

    fun f(x:X):Y
Copy the code

But in general, there are a couple of conditions that need to be met for a Function to be called a Pure Function.

  1. For the same input value x, you must get the same output value y.
  2. There are no side effects when executing f.

Here we have a new term, side effect. Let’s start with wikipedia’s explanation of Side Effect:

In computer science, a function side effect is a function that, when called, has additional effects on the calling function in addition to returning its value. For example, modify global variables (variables outside a function) or modify parameters.

That is, any operation that changes the external state will be considered as a side effect, including but not limited to

  1. I/O operations. Such as reading files or output in the console, print log.
  2. Modify external variables, or modify parameters to the function itself.
  3. Throw an exception. And so on.

Side Effect is considered a bad thing in functional programming. Because it’s so unmanageable, like the common system.CurrentTimemillis () method. Every time we call this method, it returns a different value, which is called uncontrollable. The readLine() function, for example, has no way of knowing which line it is going to read.

But conversely, if we don’t live in the “nice” world of pure functions. In our world, almost nothing can be done without side effect. Without Side Effect we wouldn’t even receive user input, because user input, like a screen click, is a Side Effect. To solve this problem, Monad was introduced in Haskell (a purely functional programming language) to control Side Effect.

Monad

We say Side Effect is not good, but it is useful. We do not want to eliminate the Side Effect, we hope that the Side Effect is under our control and controllable. So Monad is introduced to control the Side Effect. Monad in functional programming, there are too many tutorials, articles to explain. But after I saw it, I was in a fog. Someone even said:

The curse of the monad is that once you get the epiphany, once you understand – “oh that’s what it is” – you lose the ability to explain it to anybody.

Monad’s curse is that once you understand him, you lose the ability to explain him to others.

I can’t say that curse has been removed in this article, I can only do my best to explain the concept in a language that an Android developer can read, so I mention in the preface that this article can cause serious discomfort.

So, what is Monad?

Let’s go back to our pure function, a pure function for example

f : x -> y

How can we give him a controlled Side Effect? One way to do this is to put all the Side effects in a box and use it as an output value along with y. Such as

f : x -> S y

S represents a series of Side effects-related operations before the output y. But the problem with this is if we do several Side effects in a row. We’re going to take this S with us, so let’s say we have two functions f,g:

f : x -> S y g : y -> S z

So after we call f and g consecutively, the result will be:

f (g(x)) : x -> S(Sz)

This is where Monad comes in. Obviously, we need the ability to “combine”, to combine two S’s into one, and we prefer to combine multiple S’s into one, like this:

f(g(x)) : x -> S z

A Monad we simply define as having a box S that contains the following two operations:

  1. An operation into the box (return in Haskell) return: x -> S x In the world of RxJava, is more like a series of operators that generate Observable, such ascreate.just.fromXXXAnd so on. Such as:
    val x = 10
    Observable.just(x)
    // Here we enter the world of Monad, which is an Observable
Copy the code
  1. A “mystical” operation called bind(==> in Haskell). That’s our ability to combine, it’s going to take a function f: x -> M y and connect two functions with Monad.

Haskell: (>>=) :: m x -> (x -> m y) -> m y

I’m sure you don’t understand it, but let’s use the Language of Java to describe it. We know that functions in Java are not first-class citizens and cannot be passed directly as arguments to methods. We can only use an interface to simulate a function. Let’s define our function:

public interface Function<T.R>{
    R apply(T t)
}
Copy the code

T is our input, and R is our output. (This is actually the Function interface in Java 8).

F: x ->M y = f: x ->M y = M y = M y

public class Monad<X> {
    public Monad<Y> bind(Function<X,Monad<Y>> function) 
}
Copy the code

That is, what we just said, the ability to combine. We convert our Monad< x > to Monad< y >, not Monad

>, by receiving an x -> M y. But essentially, what we got was Monad

wrapped in our original Monad

, just formally we got Monad

. This part can be expressed more succinct by kotlin:


class Monad<X>

fun<X,Y> Monad<X>.bind(function:(X) -> Monad<Y>) :Monad<Y>
Copy the code

In my last post, I said that

Collection can be combined, transformed and so on through High Oroder Function, so An Observable as one of the Collection can also be combined and transformed.

In fact, this sentence is wrong, because in the last article, we did not have the knowledge of Monad, functions, etc., we can only understand it this way. And it’s the Monad that gives the Observable the ability to transform. Conclusion 1:

Observable is a Monad

If getting started with RxJava is from RxJava1 and Throw line big guy’s RxJava Detailed for Android developers this article. You’ll know that RxJava 1 has a lift() operator. The parent operator of almost all operators, which is actually a concrete implementation of bind in Monad. Some people also interpret flatMap as a bind in Monad, which I personally think is incorrect. Even though they have the same signature, the effect is the same. But the implementation of the flatMap operator in RxJava is quite different from the other operators. Lift () does the abstract work for all operators in RxJava 1.x. Transform Observable X into Observable Y by receiving a function called x-> Observable Y. In RxJava2, because of performance issues, the lift() operator implementation is changed to directly inherit an Observable that writes the lift operation to a subscribeActual(). This reduces the performance cost, but makes it more difficult to write an operator correctly.

To be a Monad, you need to have only a return and bind. You need to have a Monad that meets the following three rules

  1. Left unit:

    id(X).bind(f:X -> Monad<Y>) = Monad<Y>

    When bind adds id to the left, it gets the result of bind itself. I’m going to write it in RxJava

        Observable.just(1)
                .flatMap(new Function<Integer, ObservableSource<String>>() {
                    @Override
                    public ObservableSource<String> apply(Integer integer) throws Exception {
                        returnObservable.just(integer.toString()); }})// There is no difference between the flatMap Observable after just and the observable. Just ("1")

Copy the code
  1. Right unit:

    Monad(X).bind(id) = Monad<X>

    So if you combine Monad with the id function, what you get is Monad in RxJava

        Observable observable = Observable.just(1)
                .flatMap(new Function<Integer, ObservableSource<Integer>>() {
                    @Override
                    public ObservableSource<Integer> apply(Integer integer) throws Exception {
                        returnObservable.just(integer); }})// There is no difference between the flatMap Observable and our Observable. Just (1)
Copy the code
  1. Associative law:
Monad<X>.bind(function :X -> Monad<Y>).bind(function:Y -> Monad<Z>) 
    = Monad<X>.bind(function:x -> Monad<Y>.bind(function: Y -> Monad<Z>))
Copy the code

That is, merge the last two monads, merge the Monad, merge the Monad. The effect is the same as when you merge Monad,Monad, and Monad. I’m going to write it in RxJava

        Observable observable1 = Observable.just(2)
                .flatMap(new Function<Integer, ObservableSource<String>>() {
                    @Override
                    public ObservableSource<String> apply(Integer integer) throws Exception {
                        return Observable.just(integer.toString());
                    }
                })
                .flatMap(new Function<String, ObservableSource<Double>>() {
                    @Override
                    public ObservableSource<Double> apply(String s) throws Exception {
                        returnObservable.just(Double.valueOf(s)); }}); Observable observable2 = Observable.just(2)
                .flatMap(new Function<Integer, ObservableSource<Double>>() {
                    @Override
                    public ObservableSource<Double> apply(Integer integer) throws Exception {
                        return Observable.just(integer.toString())
                                .flatMap(new Function<String, ObservableSource<Double>>() {
                                    @Override
                                    public ObservableSource<Double> apply(String s) throws Exception {
                                        returnObservable.just(Double.valueOf(s)); }}); }});// Here observable1 is equivalent to Observable2
Copy the code

A box that obeies the above three rules and has return/ ID and bind is called a Monad. After we understand Monad, we will find a lot of things around us, even some of the things we use every day, he is Monad. For example, LINQ in C# is Monad, the new CompletableFuture and Stream apis introduced in Java 8 are Monad, Promise in JavaScript is Monad, and Observable in RxJava is Monad. This explains why many people who understand the RxJava source code don’t understand why the Observable operator is written as an Observable within an Observable. The final form of mutual notification. For simplicity, we’ll use Kotlin.

        Observable.just(1.2.3.4)
            .map{x -> x +1}
            .filter { x -> x >3 }
            .flatMap { x -> Observable.just(x,x+2)}Copy the code

The generated Observable is an ObservableFlatMap(ObservableFilter(ObservableMap(ObseravbleJust(1,2,3,4))))) nested in a box of ObservableFlatMap(ObservableFilter(ObservableMap(ObseravbleJust(1,2,3,4))))). The one thing that gives it the ability to nest, and omits it into just one Observable’s power, is Monad. So we come to the conclusion 2

A concrete implementation of bind in the Observable operator Monad.

While this doesn’t apply to all operators, there are some special operators that jump out of Monad and return to our normal Java/Kotlin world. Subscribe,blockingFirst(),forEach(), and so on. These are our exits from the Monad/Observable world.

Summary: In this article, I’ve focused on the concept of function programming and Monad, focusing on the close relationship between Monad and Observable. In my opinion, if you’re not interested in functional programming, you don’t have to worry too much about what Monad means, just think of it as a solution for assembling transformations of collections.

References (some links may require ladders)

  1. Pure Function
  2. functional programming
  3. Function side effect
  4. Functor and monad examples in plain Java