Ergonomic State Management: Part 1

Episode #98 • Apr 13, 2020 • Subscriber-Only

The Composable Architecture is robust and solves all of the problems we set out to solve (and more), but we haven’t given enough attention to ergonomics. We will enhance one of its core units to be a little friendlier to use and extend, which will bring us one step closing to being ready for production.

Ergonomic State Management: Part 1
Introduction
00:05
The architecture's surface area
01:45
Free functions
03:47
Reducer as a struct
08:38
Reducer methods
12:47
Updating the app's modules
19:32
Till next time
26:58

Unlock This Episode

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

Introduction

We have now spent many, many weeks building up our Composable Architecture from first principles. Its core design was motivated by trying to solve five problems that we found crucial for any application architecture to solve.

We then refined this design by addressing a couple memory leaks and a potential performance concern with how our architecture originally interfaced with SwiftUI. Tackling the latter issue provided us with an opportunity to enhance our architecture to be more adaptable to various situations, which allowed us to share core business logic across many platforms while refining the way each platform interacts with that shared logic.

We still have many, many things we want to explore in our architecture, but with these leaks and performance concerns addressed, we think it’s time to package things up to use in our applications. We could maybe even share it with the world as an open source project.

But before we do, we feel there is still some room for improvement. For one thing, we haven’t spent a ton of time on the ergonomics of the Composable Architecture. The core library is pretty small: less than a couple hundred lines of code. But even with such little surface area, I think we can take inspiration from earlier episodes of Point-Free as well as new Swift features to smooth out some of the rough edges around using these APIs.

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. Now that the Reducer type is a proper struct we can provide specialized initializes for common use cases. For example, it often happens that a reducer doesn’t need to do any side effects, and therefore it doesn’t need to return anything and its environment could be Void. Create an initializer on Reducer for when Environment == Void that allows us to create a reducer without having to worry about side effects.

    Solution
    extension Reducer where Environment == Void {
      init(_ reducer: @escaping (inout Value, Action) -> Void) {
        self.reducer = { state, action, _ in
          reducer(&state, action)
          return []
        }
      }
    }
    
  2. Continuing the previous exercise, there is another form of reducer that some may like more than our current shape. Right now we have immediate access to the environment in the reducer, which means we technically could invoke the effects right there in the reducer. We should never do that, but it’s technically possible.

    There is a slight alteration we can make to the reducer so that it is not handed an environment, but instead it will return a function that takes an environment and then returns effects. Create a static function on Reducer called strict that allows one to create reducers from that shape.

    Solution
    extension Reducer {
      static func strict(
        _ reducer: @escaping (inout Value, Action) -> (Environment) -> [Effect<Action>]
      ) -> Reducer {
        .init { value, action, environment in
          reducer(&value, action)(environment)
        }
      }
    }