Skip to content

Commit 88bc048

Browse files
committed
3.1 Reworked chapter
1 parent d34bffc commit 88bc048

File tree

1 file changed

+22
-19
lines changed

1 file changed

+22
-19
lines changed

Part 3 - Taming the sequence/1. Side effects.md

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# PART 3 - Taming the sequence
2+
\
3+
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 some good practices for using Rx in bigger applications.
24

3-
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.
5+
# Side effects
46

5-
# Side effect
7+
Functions without side-effects interact with the rest of the program exclusively through their arguments and return values. When the operations within a function can affect the outcome of another function (or a subsequent call to the same function), we say that the function has side effects. Common side effects are writes to storage, logging, debugging or prints to a user interface. A more language-dependent form of side effect is the ability to modify the state of an object that is visibible to other functions, which is something that Java considers legal. A function passed as an argument to an Rx operator can modify values in a wider scope, perform IO operations or update a display.
68

7-
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.
8-
9-
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.
9+
Side effects can be very useful and are unavoidable in many cases. But they also have pitfalls. Rx developers are encouraged to avoid unnecessary side effects, and to have a clear intention when they do use them. While some cases are justified, abuse introduces unnecessary hazards.
1010

1111
## Issues with side effects
1212

1313
> 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.
1414
> 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.
1515
16-
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 objects from your lambda. We've created a simple counter as a class:
16+
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, the `final` keyword in Java protects only the reference and not the state of the referred object. Nothing stops you from modifying the state of objects from your lambda. Consider this simple counter, that is implemented as an object, rather than a primitive `int`.
1717

1818
```java
1919
class Inc {
@@ -27,7 +27,7 @@ class Inc {
2727
}
2828
```
2929

30-
We are going to use this to index the items of an observable
30+
An instance of `Inc` can have its state modified even if it is declared as final. We are going to use this to index the items of an observable. Note that, while Java didn't force us to explicitly declare it as `final`, it would produce an error if we tried to change the reference while also using the reference in our lambda.
3131

3232
```java
3333
Observable<String> values = Observable.just("No", "side", "effects", "please");
@@ -119,8 +119,9 @@ The result now is valid. We removed the shared state between the two subscriptio
119119
## do
120120

121121
There are cases where we do want a side effect, for example when 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:
122+
122123
1. We are mixing the less interesting code of logging with the critical code of our subscription
123-
2. If we wanted to log an intermediate state in our pipeline, e.g. before and after mapping, we would have to introduce separate subscriptions just for that.
124+
2. If we wanted to log an intermediate state in our pipeline, e.g. before and after mapping, we would have to to introduce an additional subscription just for that, which won't necessarily see _exactly_ what the consumer saw and at the time when they saw it.
124125

125126
The next family of methods helps us declare side effects in a tidier manner.
126127

@@ -133,7 +134,7 @@ public final Observable<T> doOnNext(Action1<? super T> onNext)
133134
public final Observable<T> doOnTerminate(Action0 onTerminate)
134135
```
135136

136-
As we can see, they take actions to perform when items are emitted. They also return the observable, which means that we can use them between operators in our pipeline. In some cases, you could achieve the same result using `map` or `filter`. Using `doOn_` is better because it documents your intention to have a side effect. Here's an example
137+
As we can see, they take actions to perform when items are emitted. They also return the `Observable<T>`, which means that we can use them between operators in our pipeline. In some cases, you could achieve the same result using `map` or `filter`. Using `doOn*` is better because it documents your intention to have a side effect. Here's an example
137138

138139
```java
139140
Observable<String> values = Observable.just("side", "effects");
@@ -198,7 +199,7 @@ public final Observable<T> doOnSubscribe(Action0 subscribe)
198199
public final Observable<T> doOnUnsubscribe(Action0 unsubscribe)
199200
```
200201

201-
Subscription and unsubscription are not events that are emitted by an observable. They can still be seen as events in general and you may want to perform some actions when they occur. Most likely you'll be using them for logging purposes.
202+
Subscription and unsubscription are not events that are emitted by an observable. They can still be seen as events in a general sense and you may want to perform some actions when they occur. Most likely, you'll be using them for logging purposes.
202203

203204
```java
204205
ReplaySubject<Integer> subject = ReplaySubject.create();
@@ -232,7 +233,7 @@ Subscription over
232233

233234
## Encapsulating with AsObservable
234235

235-
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
236+
Rx is designed in the style of functional programming, but it exists within an object-oriented environment. We also have to protect against object-oriented dangers. Consider this naive implementation for a service that returns an observable.
236237

237238
```java
238239
public class BrakeableService {
@@ -245,7 +246,7 @@ public class BrakeableService {
245246
}
246247
```
247248

248-
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`
249+
The code above does not prevent a naughty consumer from changing your `items` with one of their own. After that happens, subscriptions done 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`
249250

250251
```java
251252
public class BrakeableService {
@@ -263,29 +264,31 @@ public class BrakeableService {
263264
}
264265
```
265266

266-
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<T>`. `Subject`s extend `Observable` and we can cast our subject
267+
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<T>`, which is an immutable object. `Subject`s extend `Observable` and we can cast our subject
267268

268269
```java
269270
public Observable<String> getValuesUnsafe() {
270271
return items;
271272
}
272273
```
273274

274-
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.
275+
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.
276+
277+
#### asObservable
275278

276-
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.
279+
The idea behind the `asObservable` method is to wrap extensions of `Observable` into an actual `Observable` that can be safely shared, since `Observable` is immutable.
277280

278281
```java
279282
public Observable<String> getValues() {
280283
return items.asObservable();
281284
}
282285
```
283286

284-
Now we have properly protected our `Subject`. This protection is 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.
287+
Now we have properly protected our `Subject`. This protection is 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. Observables that are constructed with any of the factory methods or operators exposed on `Observable` are immutable, provided that we don't introduce side-effects ourselves, as we saw in [Issues with side effects](/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md#issues-with-side-effects).
285288

286289
## Mutable elements cannot be protected
287290

288-
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:
291+
As one might expect, an Rx pipeline forwards 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 every position in the pipeline that uses them. Consider the following mutable class:
289292

290293
```java
291294
class Data {
@@ -298,7 +301,7 @@ class Data {
298301
}
299302
```
300303

301-
Now we show an observable that use it and two subscribers to it.
304+
Now we show an observable of that type and two subscribers.
302305

303306
```java
304307
Observable<Data> data = Observable.just(
@@ -315,7 +318,7 @@ Output
315318
2: Garbage
316319
```
317320

318-
The first subscriber is the first to be called for each item. It 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 declared by the producer. A developer needs to have a deep understanding of both Rx, Java and their system in specific to reason about the sequence of modifications, and therefore to argue that such code would run according to their intention. It is simpler to avoid mutable state altogether. Observables should be seen as a sequence of resolved events.
321+
The first subscriber is the first to be called for each item. Its action is to modify the data. Once the first subscriber is done, the same reference is also passed to the second subscriber, only now the data is changed in a way that was not declared in the producer. A developer needs to have a deep understanding of Rx, Java and their environment in order to reason about the sequence of modifications, and then argue that such code would run according to a plan. It is simpler to avoid mutable state altogether. Observables should be seen as a sequence notifications about resolved events.
319322

320323

321324

0 commit comments

Comments
 (0)