A video series exploring functional programming and Swift.
#72 • Monday Sep 9, 2019 • Subscriber-only

Modular State Management: Reducers

In exploring four forms of composition on reducer functions, we made the claim that it gave us the power to fully isolate app logic, making it simpler and easier to understand. This week we put our money where our mouth is and show just how modular these reducers are!

This episode builds on concepts introduced previously:

#72 • Monday Sep 9, 2019 • Subscriber-only

Modular State Management: Reducers

In exploring four forms of composition on reducer functions, we made the claim that it gave us the power to fully isolate app logic, making it simpler and easier to understand. This week we put our money where our mouth is and show just how modular these reducers are!

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

For the past 4 weeks we have been exploring an application architecture that heavily takes inspiration from functional programming. It’s primary, atomic unit is just a function, known as a reducer, and we discovered many different ways these functions can be composed together, which is a story that has played out many times on Point-Free.

We started this exploration because we saw that although SwiftUI solves many problems that we face in building an application, and does so in a beautiful and powerful way, there are still some things it doesn’t solve. In particular, we need to know how to do things like:

  • Create complex app state models, ideally using simple value types.
  • Have a consistent way to mutate the app state instead of just littering our views with mutation code.
  • Have a way to break a large application down into small pieces that can be glued back together to form the whole.
  • Have a well-defined mechanism for executing side effects and feeding the results back into our application.
  • Have a story for testing our application with minimal setup and effort.

So far we have solved about two and half of these problems:

  • We now model state as a simple value type.
  • We now mutate our state in a consistent way.
  • And finally, we were able to break down a very large application-wide reducer into small, screen-specific reducers by using a variety of compositions.

However, we consider the last point to only be solving half of the modularity story when it comes to this architecture. Although our reducers can be split up and moduralized, the views that render the state and send actions to the store cannot. They still operate on the full universe of app state and app actions.

If we were able to focus the store on just the state and actions a particular view cares about, then we increase our chances that the view could be extracted out into its own module. This would be a huge win. The inability to isolate components of an application is perhaps one of the biggest sources of complexity we see in other people’s code bases. Components start to become unnecessarily entangled with each other, and it can be difficult to understand all the ways in which a component can change over time.

So today we are going to complete the story of modularizing our architecture by first directly showing what it means to moduralize and why it’s beneficial, and then by showing how our Store type supports two types of transformations that allow us to focus its intentions onto only the things the view truly cares about.

Let’s start with a quick tour of the code we’ve written so far.

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. Explore the possibilities of transforming a store that works on global values to one that works with local values. That is, a function with the following shape of signature:

    extension Store {
      func view<LocalValue>(
        /* what arguments are needed? */
        ) -> Store<LocalValue, Action> {
    
        fatalError("Unimplemented")
      }
    }
    

    Such a transformation would allow you to pass along more localized stores to views rather than forcing all views to work with the global store that holds all application state and actions.

  2. Update the FavoritePrimes view to work with a Store that concentrates entirely on the state it cares about and nothing more.

  3. When instantiating this updated FavoritePrimes view, use the view method on Store to focus in on this concentrated state.

  4. Modularity (via Swift modules) is just one flavor of code isolation. Another common means of isolating code in Swift is via various levels of access control. How do internal, private, and fileprivate access control isolate code? What are the benefits of each scope? How does this isolation differ from module boundaries?

  5. In this episode we created a PrimeModalState struct to model the local state that the prime modal needed in its module, and then later refactored that into a tuple to reduce a little bit of boilerplate. There is another common way to pass around subsets of data in Swift: protocols! Try converting PrimeModalState to a protocol that exposes only the fields of AppState that it cares about, and fix all of the compiler errors until it works. What things unexpectedly break? Does it reduce boilerplate more than struct or tuple approach?


References

Chapters
Introduction
00:06
Recap
02:50
What does modularity mean?
07:18
Modularizing our reducers
09:17
Modularizing the composable architecture
10:53
Modularizing the favorite primes reducer
13:41
Modularizing the counter reducer
16:13
Modularizing the prime modal reducer
17:05
Till next time...
26:03