Composable State Management: State Pullbacks

Episode #69 • Aug 12, 2019 • Subscriber-Only

So far we have pulled a lot of our application’s logic into a reducer, but that reducer is starting to get big. Turns out that reducers emit many types of powerful compositions, and this week we explore two of them: combines and pullbacks.

State Pullbacks
Introduction
00:05
Combining reducers
01:41
Focusing a reducer's state
08:37
Pulling back reducers along state
12:55
Key path pullbacks
18:11
Pulling back more reducers
21:03
Till next time
24:32

Unlock This Episode

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

Introduction

We now have a very basic version of our architecture in place. We have a store class that is generic over a state type which represents the full state of our application and its generic over an action type that represents all of the user actions that can take place in our application.

  • The store class wraps a state value, which is just a simple value type, and this allows us to once and for all hook into the observer so that we can notify SwiftUI anytime a change is about to happen to our state.
  • The store class also holds onto a reducer, which is the brains of our application. It describes how to take the current state of the application, and an incoming action from the user, and produce a whole new state of the application that can be then rendered and displayed to the user.

Already this little bit of work as solved 2 of the 5 problems we outlined at the beginning of this episode.

But, as cool as all of this is, we can go further. Let’s address the problem that is starting to develop in our appReducer. Right now it’s looking pretty hefty: one giant reducer that is handling the mutations for 3 different screens. This doesn’t seem particularly scalable. If we had two dozen screens are we really going to want a single switch statement that switches over every single action of 24 different screens? That’s not going to work.

We need to investigate ways of composing reducers into bigger reducers. How can break up that one big reducer into lots of little tiny ones that do one specific thing and then glue them together to form our master appReducer? Let’s start to study that.

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. In this episode we mentioned that pullbacks along key paths satisfy a simple property: if you pull back along the identity key path you do not change the reducer, i.e. reducer.pullback(\.self) == reducer.

    Pullbacks also satisfy a property with respect to composition, which is very similar to that of map: map(f >>> g) == map(f) >>> map(g). Formulate what this property would be for pullbacks and key paths.

  2. We had to create a struct, FavoritePrimeState, to hold just the data that the favorite primes screen needed, which was the activity feed and the array of favorite primes. Is it possible to instead use a typelias of a tuple with named fields instead of the struct? Does anything need to change to get the application compiling again? Do you like this approach over the struct?

  3. By the end of this episode we showed how to make reducers work on local state by using the pullback operation. However, the reducers still operate on the full AppAction enum, even if it doesn’t care about all of the cases in that enum. Try to repeat what we did in this episode for action enums, i.e. define a pullback operation that is capable of transforming reducers that work with local actions to ones that work on global actions.

    For the state pullback we needed a key path to implement this function. What kind of information do you need to implement the action pullback?

  4. By the end of this episode we showed how to make reducers work on local state by using the pullback operation. However, all of the views still take the full global store, Store<AppState, AppAction>, even if they only need a small part of the state. Explore how one might transform a Store<GlobalValue, Action> into a Store<LocalValue, Action>. Such an operation would help simplify views by allowing them to focus on only the data they care about.

  5. We’ve seen that it is possible to pullback reducers along state key paths, but could we have also gone the other direction? That is, can we define a map with key paths too? If this were possible, then we could implement the following signature:

    func map<Value, OtherValue, Action>(
      _ reducer: @escaping (inout Value, Action) -> Void,
      value: WritableKeyPath<Value, OtherValue>
    ) -> (inout OtherValue, Action) -> Void {
      fatalError("Unimplemented")
    }
    

    Can this function be implemented? If not, what goes wrong?

    Solution

    This function cannot be implemented. When we try to return a new reducer that works on OtherValue we hit a roadblock quickly:

    func map<Value, OtherValue, Action>(
      _ reducer: @escaping (inout Value, Action) -> Void,
      value: WritableKeyPath<Value, OtherValue>
    ) -> (inout OtherValue, Action) -> Void {
      return { otherValue, action in
    
      }
    }
    

    OtherValue is a local something that we can pluck out of more global Value, but we cannot create a more global Value from the local OtherValue we have access to, so we can never call the reducer that requires this global Value.

  6. The previous exercise leads us to realize that there is something specific happening between the interplay of key paths and pullbacks when it comes to reducers. What do you think the underlying reason is that we can pullback reducers with key paths but we cannot map reducers with key paths?

References

Contravariance

Brandon Williams & Stephen Celis • Monday Apr 30, 2018

We first explored the concept of the pullback in our episode on “contravariance”, although back then we used a different name for the operation. The pullback is an instrumental form of composition that arises in certain situations, and can often be counter-intuitive at first sight.

Let’s explore a type of composition that defies our intuitions. It appears to go in the opposite direction than we are used to. We’ll show that this composition is completely natural, hiding right in plain sight, and in fact related to the Liskov Substitution Principle.

Pullback

We use the term pullback for the strange, unintuitive backwards composition that seems to show up often in programming. The term comes from a very precise concept in mathematics. Here is the Wikipedia entry:

In mathematics, a pullback is either of two different, but related processes: precomposition and fibre-product. Its “dual” is a pushforward.

Some news about contramap

Brandon Williams • Monday Oct 29, 2018

A few months after releasing our episode on Contravariance we decided to rename this fundamental operation. The new name is more friendly, has a long history in mathematics, and provides some nice intuitions when dealing with such a counterintuitive idea.

Category Theory

The topic of category theory in mathematics formalizes the idea we were grasping at in this episode where we claim that pulling back along key paths is a perfectly legimate thing to do, and not at all an abuse of the concept of pullbacks. In category theory one fully generalizes the concept of a function that maps values to values to the concept of a “morphism”, which is an abstract process that satisfies some properties with respect to identities and composition. Key paths are a perfectly nice example of morphisms, and so category theory is what gives us the courage to extend our usage of pullbacks to key paths.

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