Side effects
Tags: functional, programming, tutorial, typescript
Published: 2022-02-16 13:03:48

## Table of contents

- [Introduction](#introduction)
- [How to deal with side effects](#how-to-deal-with-side-effects)
- [Chaining side effects](#chaining-side-effects)

---

## Introduction

Initially, I wanted to talk about function purity and referential transparency at this point of the series. However, when preparing the article, I used a significant amount of references to the term "side effect". This is why I chose to introduce side effects before talking about function purity. Both concepts are related to one another, but they deserve their separate article.

So, what is a side effect? **A side effect is an interaction with the outside world**.

Now you might be wondering, what is the "outside world" then? It is anything that is not part of the _domain layer_ of the software. Here are some examples of these types of interactions:
* Reading a file, and/or writing to a file
* Making a network request (calling an API, downloading a file...)
* Reading from a global state (e.g. global variable, a parameter from the parent's closure...)
* Throwing/intercepting an exception
* For web applications, using DOM objects and methods
* Logging messages to the console

The domain layer contains all the models, logic, and rules of the domain. If you are familiar with the "hexagonal architecture", also known as "ports and adapters architecture", then this layer is at the very center of the diagram.

![Domain layer at the center, encompassed by the Application layer which "side effect" interactions with outside elements](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lnwsr8wrs0p17znxtnq.png)

Generally speaking, all the functions from the domain layer can be tested with unit tests, as there are no side effects. This is the only layer that is _pure_, all the others encompassing it have side effects.

In functional programs, functions have to be pure, i.e. side-effect-free. Why? **Because side effects are not deterministic, and they are hard to reason about**.

When we want to validate that a function works as expected, we have to be able to understand it and use automated tests. Introducing non-determinism and external references into the function makes it hard to understand it simply, and test its behavior.

That being said, a program that has no side effects is not very useful. **We need interactions with the outside world**, such as getting input from a user, writing to a database, or displaying something to the screen.

So, on the one hand, side effects are bad, but on the other hand, they are mandatory to build useful programs. What can we do about it?

## How to deal with side effects

One particularity of a side effect is that _it just happens_. In our imperative programs, a side effect does not return anything. In other words, we cannot store it in a variable, or pass it as a parameter of a function.

We can easily identify functions that perform side effects, as they are the ones that do not return any value. For example:

```typescript
function trustMeNoSideEffectHere(): void {
  ...
}
```

This function's name is misleading as its return type does not match its name. The `void` type indicates that this function does not return any value. Therefore, if it does not compute any value, it is either useless or performs at least a side effect.

Of course, we can still have functions that return a value, but also perform side effects:

```typescript
function trustMeNoSideEffectHere(): number {
  let n = 21
  window.foo.bar++ // mutating the global state = side effect
  return n * 2
}
```

In an imperative program with no defined rules regarding side effects, we must read the definition of the function to understand what it does, thus losing the [type-based reasoning](https://dev.to/ruizb/why-should-we-learn-and-use-fp-5ce5#typebased-reasoning) benefit.

Furthermore, the moment we call the `trustMeNoSideEffectHere` function, the side effect immediately happens. We have to make sure the environment is ready right before calling the function, otherwise, we are exposed to undesired behaviors.

In functional programs, since functions are pure (i.e. no side effects), they must return a value, otherwise, they are pretty much useless. In addition, determinism is a key aspect of functional programming. We cannot allow a function to trigger a side effect just by calling it. As developers, we want to control when the side effect happens (remember, side effects must happen at some point, otherwise the program is pointless).

How can we make the `trustMeNoSideEffectHere` function pure, and control when the side effect (i.e. global state mutation) happens?

One way of doing it would be to wrap some parts of this function in another function, making it "lazy":

```typescript
function trustMeNoSideEffectHere(): () => number {
  let n = 21
  return () => {
    window.foo.bar++ // mutating the global state = side effect
    return n * 2
  }
}
```

What happens when we call `trustMeNoSideEffectHere()`? **We get a value** of type `() => number`, that we can store in a variable:

```typescript
const res: () => number = trustMeNoSideEffectHere()
```

Did anything happen? No. **The global state has not been mutated yet** since we have not called `res()` yet. Though, we changed the definition of `trustMeNoSideEffectHere` to make it pure: it always returns the same value, a function.

_Granted, if we call the function twice, we will get different references for the inner function: `trustMeNoSideEffectHere() === trustMeNoSideEffectHere()` would be false. So technically, we do not return the "same" value. Still, since functions equality is a complex topic, let us assume that the values are equivalent here, and move on._

Now that the function is deterministic, we can write a unit test, although it is not very useful in this case:

```typescript
expect(trustMeNoSideEffectHere()).to.be.a('function')
```

> Imagine you are testing the `launchNuclearWarheads` function. It is pretty nice to make sure that calling `launchNuclearWarheads()` does not do anything, and that it requires an extra step to actually end life on Earth.

The moment we are calling `res()`, the side effect will happen, thus propagating the non-determinism to the parent functions.

![All the parent functions of the "res" function are made non-deterministic as soon as "res" is called](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufp1e7ajt2aabqf19fg1.png)

At this point, it is not possible to write unit tests for the `main`, `serviceA`, and `serviceB` functions. What can we do to make these functions deterministic? We can apply the same strategy as before: returning functions that wrap the side effect (cf. the blue wrapping function on the following picture).

![By returning wrapper functions (containing the "res" function call) up the chain, only the top parent function (main) is made non-deterministic when finally calling the wrapper function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sremsk3xmkexdcotn2xf.png)

We managed to make the `serviceA` and `serviceB` functions side-effect-free. Only the `main` function is impure and _performs_ side effects, which is convenient: now we know where all the side effects _actually_ happen in the program.

## Chaining side effects

What happens when we want to chain side effects? For example:
* First we want to ask for the user's name
* Then we want to perform some API call with the user's name (e.g. to retrieve some information regarding its origin)
* Finally we want to display the result on the screen

Again, we can use the same technique: make any side effect lazy by wrapping it inside a function.

(here we are assuming the code is run in a browser)

```typescript
function askForUserName(): () => string {
  return () => prompt('What is your name?')
}

function getUserNameOrigin(userName: string): () => Promise<string> {
  const sanitizedName = userName.toLowerCase()
  return () => fetch(`/get-origin/${sanitizedName}`)
    .then(res => res.json())
    .then(res => res.userNameOriginText)
}

function displayResult(text: string): () => void {
  return () => {
    document.getElementById('origin-text')?.innerText = text
  }
}

function originNameService(): () => Promise<void> {
  return () => {
    const lazyUserName = askForUserName()
    const lazyGetUserNameOrigin = getUserNameOrigin(lazyUserName())
    return lazyGetUserNameOrigin()
      .then(originText => {
        const lazyDisplayResult = displayResult(originText)
        lazyDisplayResult()
      }) 
  }
}

function main(): void {
  const lazyOriginNameService = originNameService()
  lazyOriginNameService()
}
```

Here, only the `main` function is non-deterministic, the remaining functions are all side-effect-free, thanks to the wrapper functions.

---

That being said, writing tests for these pure functions would not be very useful, as we would not test their actual behavior:

```typescript
expect(askForUserName()).to.be.a('function')
expect(getUserNameOrigin('foo')).to.be.a('function')
expect(displayResult('bar')).to.be.a('function')
```

To overcome this, we could use Dependency Injection, a.k.a DI. This is a bit out-of-scope for this article, but essentially we would pass the actual effect (i.e. `prompt`, `fetch`...) as a dependency of the function, and we would use a mocked/controlled version of this effect for writing the unit tests.

For example:

```typescript
interface Dependencies {
  prompt: (text: string) => string
}

function askForUserName({ prompt }: Dependencies): () => string {
  return () => prompt('What is your name?')
}

const lazyUserName: () => string =
  askForUserName({ prompt: window.prompt })
```

```typescript
const fakePrompt = (text: string) => 'foo'

const lazyUserName: () => string =
  askForUserName({ prompt: fakePrompt })

expect(lazyUserName).to.be.a('function')
expect(lazyUserName()).to.equal('foo')
```

---

What if we wrote a simple type alias for the introduction of laziness?

```typescript
type IO<A> = () => A

function askForUserName(): IO<string> { ... }
function getUserNameOrigin(userName: string): IO<Promise<string>> { ... }
function displayResult(text: string): IO<void> { ... }
function originNameService(): IO<Promise<void>> { ... }
function main(): void { ... }
```

And what about a special alias for the lazy promises?

```typescript
type Task<A> = IO<Promise<A>>

function askForUserName(): IO<string> { ... }
function getUserNameOrigin(userName: string): Task<string> { ... }
function displayResult(text: string): IO<void> { ... }
function originNameService(): Task<void> { ... }
function main(): void { ... }
```

The type of `main` is `() => void`, so in other words, `main: IO<void>`. Also, `askForUserName` and `originNameService` do not take any argument, so they are already lazy. We can simplify their definition with:

```typescript
const askForUserName: IO<string> = ...
const originNameService: Task<void> = ...
const main: IO<void> = ...
```

Now _imagine_ if these `IO` and `Task` types had some behavior attached to them, allowing us to do something such as:

```typescript
const originNameService: Task<void> =
  Task.fromIO(askForUserName)
    .andThen(getUserNameOrigin)
    .andThen(text => Task.fromIO(displayResult(text)))
```

This is how we can contain side effect declarations inside values, that we can compose with other values, thus describing more and more complex side effects. To run the side effects, we can simply run the final value obtained from the composition:

```typescript
// assuming we have some behavior attached to our Task<void> value

originNameService.runEffect()
```

---

Here, I presented one approach we can use to handle side effects.

**We have separated their declaration from their execution**, giving us more flexibility to combine these effects, and more control over what happens in our program.

Functional languages and libraries provide "standard" tools to deal with side effects, so we do not have to build them ourselves. Haskell has a built-in `IO` data type. The [fp-ts](https://github.com/gcanti/fp-ts) TypeScript library provides both `IO` for synchronous side effects, and `Task` for asynchronous ones. The [cats-effect](https://typelevel.org/cats-effect/) Scala library provides the `IO` data type as well.

Writing functional programs will undoubtedly lead you to use these data types to deal with side effects.

The goal is to have as many side-effect-free functions as possible in our programs, by **leveraging side effects declarations, and isolating side effects execution.**

This concludes the chapter on side effects. Hopefully, you enjoyed it and understood what side effects are, and how you can deal with them.

The next step is to talk about function purity and referential transparency. As always, feel free to leave a comment :) 

See you then!

---

Special thanks to [Tristan Sallé](https://twitter.com/TristanSoullz) for reviewing the draft of this article.

Photo by <a href="https://unsplash.com/@xavi_cabrera?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Xavi Cabrera</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>.

Pictures made with [Excalidraw](https://excalidraw.com/).