Effectful State Management: Synchronous Effects

Episode #76 • Oct 14, 2019 • Subscriber-Only

Side effects are one of the biggest sources of complexity in any application. It’s time to figure out how to model effects in our architecture. We begin by adding a few new side effects, and then showing how synchronous effects can be handled by altering the signature of our reducers.

Synchronous Effects
Introduction
00:05
Adding some simple side effects
01:19
Effects in reducers
08:35
Reducers as pure functions
11:25
Effects as values
16:01
Updating our architecture for effects
17:14
Reflecting on our first effect
23:44

Unlock This Episode

Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.

Introduction

We’re now in the final stretches of defining our application architecture. We’ve solved three of the five problems that pop up in any moderately complex application, including how the entirety of app state should be modeled in a simple value type, how this state should be mutated using simple composable functions called reducers, and how an application can be decomposed into smaller pieces that can live in their own modules and be understood in isolation.

It’s now time to solve one of the biggest problems we face when building applications: side effects. We’ve talked about side effects a bunch on Point-Free. In fact, our very second episode was dedicated to identifying side effects, understanding how they can impact function composition, and recovering composition by pushing them to the boundaries of the function. We’ve also spent several episodes on dependency injection to show that it’s completely possible to control side effects in a lightweight way using what we called the “Environment”, and how this unlocks our ability to easily simulate and test certain states in our applications that would have otherwise been very difficult.

We want to understand how to model side effects in this reducer-based architecture. There are many approaches to this problem, all with their own trade-offs, but as with everything we do on Point-Free, we want our solution to be transformable and composable.

So, we are going to identify a few flavors of side-effects in our application, and then slowly step through how we can isolate and control the effects. Let’s start by recalling what application we are building…

This episode is for subscribers only.

Subscribe to Point-Free

Access this episode, plus all past and future episodes when you become a subscriber.

See plans and pricing

Already a subscriber? Log in

Exercises

  1. Currently effects have no way of making changes to the app state, which is what is needed to implement the effect for loading favorite primes. One way to allow for this is to change the definition of Effect so that a mutable state value is passed to the effect:

    -typealias Effect<State> = () -> Void
    +typealias Effect<State> = (inout State) -> Void
    

    Fix the app to build with the above change. Implement the effect to load the favorite primes using this type of effect.

    Does this style of effect align with one of the central tenets of our architecture, which is that we should have a single, consistent way to mutate state? Why or why not?

  2. If instead of allowing effects to mutate state directly, what if we wanted to allow effects to send actions to the store? How could the definition of Effect be changed to allow this?

  3. Not every reducer needs to perform side effects. Write a function that can lift any side-effectless reducer into a signature that supports side effects. Such a function would have the following signature:

    typealias Effect = () -> Void
    typealias Reducer<State, Action> = (inout State, Action) -> Effect
    
    func pure<State, Action>(
      _ reducer: (inout State, Action) -> Void
    ) -> Reducer<State, Action> {
      fatalError("Unimplemented")
    }
    

References

Side Effects

Brandon Williams & Stephen Celis • Monday Feb 5, 2018

We first discussed side effects on the second episode of Point-Free. In that episode we showed how side effects are nothing but hidden inputs or outputs lurking in the signature of our functions. We also showed that making that implicit behavior into something explicit makes our code most understandable and testable.

Side effects: can’t live with ’em; can’t write a program without ’em. Let’s explore a few kinds of side effects we encounter every day, why they make code difficult to reason about and test, and how we can control them without losing composition.

Dependency Injection Made Easy

Brandon Williams & Stephen Celis • Monday May 21, 2018

One of the easiest ways to control side effects is through the use of “dependency injection.” In an early episode of Point-Free we showed a lightweight way to manage dependencies that gets rid of a lot of the boilerplate that is common in the Swift community.

Today we’re going to control the world! Well, dependencies to the outside world, at least. We’ll define the “dependency injection” problem and show a lightweight solution that can be implemented in your code base with little work and no third party library.

Redux Middleware

Redux, at its core, is very simple and has no single, strong opinion on how to handle side effects. It does, however, provide a means of layering what it calls “middleware” over reducers, and this third-party extension point allows folks to adopt a variety of solutions to the side effect problem.

Redux Thunk

Redux Thunk is the recommended middleware for basic Redux side effects logic. Side effects are captured in “thunks” (closures) to be executed by the store. Thunks may optionally utilize a callback argument that can feed actions back to the store at a later time.

ReSwift

ReSwift is one of the earliest, most popular Redux-inspired libraries for Swift. Its design matches Redux, including its adoption of “middleware” as the primary means of introducing side effects into a reducer.

SwiftUIFlux

Thomas Ricouard

An early example of Redux in SwiftUI. Like ReSwift, it uses “middleware” to handle side effects.

Elm: A delightful language for reliable webapps

Elm is both a pure functional language and framework for creating web applications in a declarative fashion. It was instrumental in pushing functional programming ideas into the mainstream, and demonstrating how an application could be represented by a simple pure function from state and actions to state.

Redux: A predictable state container for JavaScript apps.

The idea of modeling an application’s architecture on simple reducer functions was popularized by Redux, a state management library for React, which in turn took a lot of inspiration from Elm.

Composable Reducers

Brandon Williams • Tuesday Oct 10, 2017

A talk that Brandon gave at the 2017 Functional Swift conference in Berlin. The talk contains a brief account of many of the ideas covered in our series of episodes on “Composable State Management”.