A video series exploring functional programming and Swift.
#76 • Monday Oct 14, 2019 • Subscriber-only

Effectful State Management: Synchronous Effects

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.

This episode builds on concepts introduced previously:

#76 • Monday Oct 14, 2019 • Subscriber-only

Effectful State Management: Synchronous Effects

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.

This episode builds on concepts introduced previously:


Subscribe to Point‑Free

This episode is for subscribers only. To access it, and all past and future episodes, become a subscriber today!

See subscription optionsorLog in

Sign up for our weekly newsletter to be notified of new episodes, and unlock access to any subscriber-only episode of your choosing!

Sign up for free episode

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…

Subscribe to Point-Free

👋 Hey there! Does this episode sound interesting? Well, then you may want to subscribe so that you get access to this episodes and more!


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”.

Chapters
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