From a205030c4ff88bd4dbbd6138b537f29f80f835f2 Mon Sep 17 00:00:00 2001 From: Chris Froussios Date: Sat, 11 Apr 2015 16:04:34 +0200 Subject: [PATCH 1/2] 3.1 --- .../1. Side effects.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/Part 3 - Taming the sequence/1. Side effects.md b/Part 3 - Taming the sequence/1. Side effects.md index 8135394..d14d9c4 100644 --- a/Part 3 - Taming the sequence/1. Side effects.md +++ b/Part 3 - Taming the sequence/1. Side effects.md @@ -1,3 +1,82 @@ +# PART 3 - Taming the sequence + +So far we've learned how to create observables and how to extract relevant data from observables. In this chapter we will go beyond what is necessary for simple examples and discuss more advanced functionality, as well as the practices of using Rx in reality, rather than small examples. + +# Side effect + +When the operations within a function can affect the outcome of another piece of code, we say that the function has side effect. Functions without side-effects interact with the rest of the program exclusively through their arguments and return values. Common side effects are writes to storage, logging, debugging or prints to an interface. + +Side effects can be useful. They also have pitfalls. Any function within an Rx operator can modify a value in a wider score, perform IO operations or update a display. Rx developers are encouraged to try and avoid them, and to have a clear intention when they use side effects. + +## Issues with side effects + +> Functional programming in general tries to avoid creating any side effects. Functions with side effects, especially which modify state, require the programmer to understand more than just the inputs and outputs of the function. The surface area they are required to understand needs to now extend to the history and context of the state being modified. This can greatly increase the complexity of a function, and thus make it harder to correctly understand and maintain. +> Side effects are not always accidental, nor are they always intentional. An easy way to reduce the accidental side effects is to reduce the surface area for change. The simple actions coders can take are to reduce the visibility or scope of state and to make what you can immutable. You can reduce the visibility of a variable by scoping it to a code block like a method. You can reduce visibility of class members by making them private or protected. By definition immutable data can't be modified so cannot exhibit side effects. These are sensible encapsulation rules that will dramatically improve the maintainability of your Rx code. + +We start with an example of an implementation with a side effect. Java doesn't allow references to non-final variables from lambdas (or anonymous implementations in general). However, it won't stop you from modifying the state of object from your lambda. We've created a simple counter as an object: + +```java +class Inc { + private int count = 0; + public void inc() { + count++; + } + public int getCount() { + return count; + } +} +``` + +We are going to use this to index the items of an observable + +```java +Observable values = Observable.just("No", "side", "effects", "please"); + +Inc index = new Inc(); +Observable indexed = + values.map(w -> { + index.inc(); + return w; + }); +indexed.subscribe(w -> System.out.println(index.getCount() + ": " + w)); +``` +Output +``` +1: No +2: side +3: effects +4: please +``` + +So far it appears ok. Let's see what happens when we try to subscribe to that observable a second time. + +```java +Observable values = Observable.just("No", "side", "effects", "please"); + +Inc index = new Inc(); +Observable indexed = + values.map(w -> { + index.inc(); + return w; + }); +indexed.subscribe(w -> System.out.println("1st observer: " + index.getCount() + ": " + w)); +indexed.subscribe(w -> System.out.println("2nd observer: " + index.getCount() + ": " + w)); +``` +Output +``` +1st observer: 1: No +1st observer: 2: side +1st observer: 3: effects +1st observer: 4: please +2nd observer: 5: No +2nd observer: 6: side +2nd observer: 7: effects +2nd observer: 8: please +``` + +The second subscriber sees the indexing starting at 5, which is non-sense. While the bug here is straight-forward to discover, side effects can lead to bugs which are a lot more subtle. + +## Composing data in a pipeline #### Continue reading From 51e365fe364fd145f42848fac8531ff473ecc161 Mon Sep 17 00:00:00 2001 From: Chris Froussios Date: Sat, 11 Apr 2015 19:14:24 +0200 Subject: [PATCH 2/2] Wrote 3.1 --- .../1. Side effects.md | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/Part 3 - Taming the sequence/1. Side effects.md b/Part 3 - Taming the sequence/1. Side effects.md index d14d9c4..611a55f 100644 --- a/Part 3 - Taming the sequence/1. Side effects.md +++ b/Part 3 - Taming the sequence/1. Side effects.md @@ -78,6 +78,245 @@ The second subscriber sees the indexing starting at 5, which is non-sense. While ## Composing data in a pipeline +The safest way to use state in Rx is to include it in the data emitted. We can pair items with their indices using `scan`. + +```java +class Indexed { + public final int index; + public final T item; + public Indexed(int index, T item) { + this.index = index; + this.item = item; + } +} +``` + +```java +Observable values = Observable.just("No", "side", "effects", "please"); + +Inc index = new Inc(); +Observable> indexed = + values.scan( + new Indexed(0, null), + (prev,v) -> new Indexed(prev.index+1, v)) + .skip(1); +indexed.subscribe(w -> System.out.println("1st observer: " + w.index + ": " + w.item)); +indexed.subscribe(w -> System.out.println("2nd observer: " + w.index + ": " + w.item)); +``` +Output +``` +1st observer: 1: No +1st observer: 2: side +1st observer: 3: effects +1st observer: 4: please +2nd observer: 1: No +2nd observer: 2: side +2nd observer: 3: effects +2nd observer: 4: please +``` + +The result now is valid. We removed the shared state between the two subscriptions and now they can't affect eachother. + +## doOnEach, doOnNext, doOnError, doOnCompleted + +The are cases where we do want a side effect, for example, for logging. The `subscribe` method always has a side effect, otherwise it is not useful. We could put our logging in the body of a subscriber but then we would have two disadvantages: +1. We are mixing the less interesting code for logging with the critical code of our subscription +2. If we wanted to log an intermediate state in our pipeline, we would have to introduce subscriptions just for that. + +The next family of methods helps us with that. + +```java +public final Observable doOnCompleted(Action0 onCompleted) +public final Observable doOnEach(Action1> onNotification) +public final Observable doOnEach(Observer observer) +public final Observable doOnError(Action1 onError) +public final Observable doOnNext(Action1 onNext) +public final Observable doOnTerminate(Action0 onTerminate) +``` + +As we can see, they take actions to perform when items are emitted, which means that there are going to be side effects. They also return the observable, which means that we can use them between operators in our pipeline. In some cases, you could also do the same thing using `map` or `filter`. Using `doOnNext` is better because it documents your intention to have a side effect. + +Here's an example + +```java +Observable values = Observable.just("side", "effects"); + +values + .doOnEach(new PrintSubscriber("Log")) + .map(s -> s.toUpperCase()) + .subscribe(new PrintSubscriber("Process")); +``` +Output +``` +Log: side +Process: SIDE +Log: effects +Process: EFFECTS +Log: Completed +Process: Completed +``` + +Here we reused our custom subscriber from previous chapters. The "do" methods are not affected by the transformations later in the pipeline. We can log what our service produces regardless of what the consumer actually consumes. Consider the following service: + +```java +static Observable service() { + return Observable.just("First", "Second", "Third") + .doOnEach(new PrintSubscriber("Log")); +} +``` + +Then we use it: + +```java +service() + .map(s -> s.toUpperCase()) + .filter(s -> s.length() > 5) + .subscribe(new PrintSubscriber("Process")); +``` +Output +``` +Log: First +Log: Second +Process: SECOND +Log: Third +Log: Completed +Process: Completed +``` + +We logged everything that our service produced, even though the consumer modified and filtered the results. + +The differences between the different variants for "do" should be apparent by this point. On special note is the `onTerminate`, which runs when the observable terminates with either `onCompleted` or `onError`. There are also some more special methods in the "do" family. + +```java +public final Observable doOnSubscribe(Action0 subscribe) +public final Observable doOnUnsubscribe(Action0 unsubscribe) +``` + +Subscription and unsubscription are not events emitted by an observable. They can be seen as events in general and you may want to perform when they occur. For example, you may want to allocate more resources when more subscriptions arrive. + +```java +ReplaySubject subject = ReplaySubject.create(); +Observable values = subject + .doOnSubscribe(() -> System.out.println("New subscription")) + .doOnUnsubscribe(() -> System.out.println("Subscription over")); + +Subscription s1 = values.subscribe(new PrintSubscriber("1st")); +subject.onNext(0); +Subscription s2 = values.subscribe(new PrintSubscriber("2st")); +subject.onNext(1); +s1.unsubscribe(); +subject.onNext(2); +subject.onNext(3); +subject.onCompleted(); +``` +Output +``` +New subscription +1st: 0 +New subscription +2st: 0 +1st: 1 +2st: 1 +Subscription over +2st: 2 +2st: 3 +2st: Completed +Subscription over +``` + +## Encapsulating with AsObservable + +Rx is designed like functional programming, but it exists within an object oriented environment. We also have to protect against the dangers of that paradigm. Consider this naive implementation + +```java +public class BrakeableService { + public BehaviorSubject items = BehaviorSubject.create("Later"); + public void play() { + items.onNext("Hello"); + items.onNext("and"); + items.onNext("goodbye"); + } +} +``` + +The code above does not prevent a naughty consumer from changing your `items` with one of their own. When that happens, subscriptions before the change will no longer receive items, because you are not calling `onNext` on the right `Subject` any more. We obviously need to hide access to our `Subject` + +```java +public class BrakeableService { + private final BehaviorSubject items = BehaviorSubject.create("Later"); + + public BehaviorSubject getValues() { + return items; + } + + public void play() { + items.onNext("Hello"); + items.onNext("and"); + items.onNext("goodbye"); + } +} +``` + +Now our reference is safe, but we are still exposing a reference to a `Subject`. Anyone can call `onNext` on our `Subject` and inject values in our sequence. We should only return `Observable`. `Subject`s extend `Observable` and we can cast our subject + +``` +public Observable getValues() { + return items; +} +``` + +Our API now looks safe but it isn't. Nothing is stopping a user from discovering that our `Observable` is actually a `Subject` (e.g. using `instanceof`), casting it to a `Subject` and using it like previously. + +The idea behind the `asObservable` method is to wrap extensions of `Observable` into an actual `Observable` that can be safely shared. `Observable` is only capable of emitting values. + +```java +public Observable getValues() { + return items.asObservable(); +} +``` +Observable +Now we have properly protected our `Subject`. This protection prevents not only against malicious attacks but also against mistakes. We have mentioned before that subjects should be avoided when alternatives exist, and now we've seen examples of why. Subjects introduce state to our observables. Calls to `onNext`, `onCompleted` and `onError` alter the sequence that consumers will see. + +## Mutable elements cannot be protected + +As one might expect, the Rx pipeline passes our references to objects and doesn't create copies (unless we do so ourselves in the functions we supply). Modifications to the objects will be visible to any position in the pipeline that uses them. Consider the following mutable class: + +```java +class Data { + public int id; + public String name; + public Data(int id, String name) { + this.id = id; + this.name = name; + } +} +``` + +Now we show an observable that use it and two subscribers to it. + +```java +Observable data = Observable.just( + new Data(1, "Microsoft"), + new Data(2, "Netflix") +); + +data.subscribe(d -> d.name = "Garbage"); +data.subscribe(d -> System.out.println(d.id + ": " + d.name)); +``` +Output +``` +1: Garbage +2: Garbage +``` + +The first subscriber is the first to be called for each item. He modifies the data. The second subscriber receives the same reference as the first subscriber, only now the data is changed in a way that was not intended by the producer. A developer needs to have a deep understanding of both Rx and Java to reason about the sequence of modifications, and therefore argue that such code would run as intended. It is simpler to avoid mutable state in the pipeline altogether and see sbservables should be seen as resolved events. + + + + + + + #### Continue reading