Modular State Management: Reducers

Episode #72 • Sep 9, 2019 • Subscriber-Only

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!

Reducers
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

Unlock This Episode

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

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.

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

    Solution
    extension Store {
      func view<LocalValue>(
        _ f: @escaping (Value) -> LocalValue
      ) -> Store<LocalValue, Action> {
    
        return Store<LocalValue, Action>(
          initialValue: f(self.value),
          reducer: { localValue, action in
            self.reducer(&self.value, action)
            localValue = f(self.value)
        }
        )
      }
    }
    
  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 the struct or tuple approach?

References

Why Functional Programming Matters

John Hughes • Saturday Apr 1, 1989

A classic paper exploring what makes functional programming special. It focuses on two positive aspects that set it apart from the rest: laziness and modularity.

Package Resources

Anders Bertelrud • Friday Dec 7, 2018

A Swift Evolution proposal for the Swift Package Manager to support resources.

Access Control

Apple

This chapter of the Swift Programming Language book explains access control in depth and how it affects module imports.

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