You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: Part 3 - Taming the sequence/1. Side effects.md
+22-19Lines changed: 22 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,19 +1,19 @@
1
1
# 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.
2
4
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
4
6
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.
6
8
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.
10
10
11
11
## Issues with side effects
12
12
13
13
> 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.
14
14
> 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.
15
15
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 counteras 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`.
17
17
18
18
```java
19
19
classInc {
@@ -27,7 +27,7 @@ class Inc {
27
27
}
28
28
```
29
29
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.
@@ -119,8 +119,9 @@ The result now is valid. We removed the shared state between the two subscriptio
119
119
## do
120
120
121
121
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
+
122
123
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.
124
125
125
126
The next family of methods helps us declare side effects in a tidier manner.
126
127
@@ -133,7 +134,7 @@ public final Observable<T> doOnNext(Action1<? super T> onNext)
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
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.
Rx is designed like functional programming, but it exists within an objectoriented 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.
236
237
237
238
```java
238
239
publicclassBrakeableService {
@@ -245,7 +246,7 @@ public class BrakeableService {
245
246
}
246
247
```
247
248
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`
249
250
250
251
```java
251
252
publicclassBrakeableService {
@@ -263,29 +264,31 @@ public class BrakeableService {
263
264
}
264
265
```
265
266
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
267
268
268
269
```java
269
270
publicObservable<String> getValuesUnsafe() {
270
271
return items;
271
272
}
272
273
```
273
274
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
275
278
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.
277
280
278
281
```java
279
282
publicObservable<String> getValues() {
280
283
return items.asObservable();
281
284
}
282
285
```
283
286
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).
285
288
286
289
## Mutable elements cannot be protected
287
290
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:
289
292
290
293
```java
291
294
classData {
@@ -298,7 +301,7 @@ class Data {
298
301
}
299
302
```
300
303
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.
302
305
303
306
```java
304
307
Observable<Data> data =Observable.just(
@@ -315,7 +318,7 @@ Output
315
318
2: Garbage
316
319
```
317
320
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.
0 commit comments