Pure functions and higher order functions

You don't have to remember most of the terms introduced in this chapter; the important thing is to understand how they help us write simplistic but powerful programs.

RxJava's approach has many functional ideas incorporated, so it is important for us to learn how to think in more functional ways in order to write better reactive applications.

Pure functions

A pure function is a function whose return value is only determined by its input, without observable side effects. If we call it with the same parameters n times, we are going to get the same result every single time. For example:

Predicate<Integer> even = (number) -> number % 2 == 0;
int i = 50;
while((i--) > 0) {
  System.out.println("Is five even? - " + even.test(5));
}

Each time, the even function returns False because it depends only on its input, which is the same each time and is not even.

This property of pure functions is called idempotence. Idempotent functions don't depend on time, so they can treat continuous data as infinite data streams. And this is how ever-changing data is represented in RxJava (Observable instances).

Note

Note that, here, the term "idempotence" is used in its computer science meaning. In computing, an idempotent operation is one that has no additional effect if it is called more than once with the same input parameters; in mathematics, an idempotent operation is one that satisfies this expression: f(f(x)) = f(x).

Pure functions do not cause side-effects. For example:

Predicate<Integer> impureEven = (number) -> {
  System.out.println("Printing here is side effect!");
  return number % 2 == 0;
};

This function is not pure because it prints on the output a message every time it is called. So it does two things: it tests whether the number is even, and it outputs a message as a side-effect. A side-effect is any possible observable output the function can produce, for example, triggering events and throwing exceptions and I/O, different from its return value. A side-effect also changes shared states or mutable arguments.

Think about it. If most of your program is composed of pure functions, it will be easy to scale and to run parts of it in parallel because pure functions can't conflict with each other and don't change the shared state.

Another thing that's worth mentioning in this section is immutability. Immutable objects are objects that can not change their state. A good example is the String class in Java. The String instances cannot be changed; even methods such as substring create a new instance of String without modifying the calling one.

If we pass immutable data to a pure function, we can be sure that every time it is called with this data it will return the same. With mutable objects, is not quite the same when we write parallel programs, because one thread can change the object's state. In this case, the pure function will return a different result if called, and thus will stop being idempotent.

If we store our data in immutable objects and operate over it using pure functions, creating new immutable objects in the process, we will be safe from unexpected concurrency issues. There will be no global state and no mutable state; everything will be simple and predictable.

Using immutable objects is tricky; every action with them creates new instances, and this could eat up memory. But there are methods for avoiding that; for example, reusing as much as we can from the source immutable, or making the immutable objects' lifecycles as short as possible (because short lifecycle objects are friendly to GC or caching). Functional programs should be designed to work with immutable stateless data.

Complex programs can't be composed only of pure functions, but whenever it is possible, it is good to use them. In this chapter's implementation of The Reactive Sum, we passed to map(), filter(), and combineLatest() only pure functions.

Speaking of the map() and filter() functions, we call them higher order functions.

Higher order functions

A function with at least one parameter of type function or a function that returns functions is called a higher order function. Of course, higher order functions can be pure.

Here is an example of a higher function that takes function parameters:

public static <T, R> int highSum(
  Function<T, Integer> f1,
  Function<R, Integer> f2,
  T data1,
  R data2) {
    return f1.apply(data1) + f2.apply(data2);
  }
)

It takes two functions of type T -> int/R -> int and some data in order to call them and sum their results. For example, we can do it like this:

highSum(v -> v * v, v -> v * v * v, 3, 2);

Here we sum the square of three and the cube of two.

But the idea of higher order functions is to be flexible. For example, we can use the highSum() function for a completely different purpose, say, summing strings, as shown here:

Function<String, Integer> strToInt = s -> Integer.parseInt(s);

highSum(strToInt, strToInt, "4",  "5");

So, a higher order function can be used to apply the same behavior to different kinds of input.

If the first two arguments we pass to the highSum() function are pure functions, it will be a pure function as well. The strToInt parameter is a pure function, and if we call the highSum(strToInt, strToInt, "4", "5") method n times, it will return the same result and won't have side-effects.

Here is another example of a higher order function:

public static Function<String, String> greet(String greeting) {
  return (String name) -> greeting + " " + name + "!";
}

This is a function that returns another function. It can be used like this:

System.out.println(greet("Hello").apply("world"));
// Prints 'Hellow world!'

System.out.println(greet("Goodbye").apply("cruel world"));
// Prints 'Goodbye cruel world!'

Function<String, String> howdy = greet("Howdy");

System.out.println(howdy.apply("Tanya"));
System.out.println(howdy.apply("Dali"));
// These two print 'Howdy Tanya!' and 'Howdy Dali'

Functions like these can be used to implement different behaviors that have something in common. In object-oriented programming we define classes and then extend them, overloading their methods. In functional programming, we define higher order functions as interfaces and call them with different parameters, resulting in different behaviors.

These functions are first-class citizens; we can code our logic using only functions, chaining them, and handling our data, transforming, filtering, or accumulating it into a result.

RxJava and functional programming

Functional concepts such as pure functions and higher order functions are very important to RxJava. RxJava's Observable class is an implementation of a fluent interface. This means that most of its instance methods return an Observable instance. For example:

Observable mapped = observable.map(someFunction);

The map() operator returns a new Observable instance, emitting the data transformed by it. Operators such as map() are clearly higher order functions, and we can pass other functions to them. So, a typical RxJava program is represented by a chain of operators chained to an Observable instance to which multiple subscribers can subscribe. These functions chained together can benefit from the topics covered in this chapter. We can pass lambdas to them instead of anonymous interface implementations (as we saw with the second implementation of the Reactive Sum), and we should try working with immutable data and pure functions when possible. This way, our code will be simple and safe.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.214.27