A new Swift video series exploring functional programming and more.
#69 • Monday Aug 12, 2019 • Subscriber-only

Composable State Management: State Pullbacks

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.

This episode builds on concepts introduced previously:

#69 • Monday Aug 12, 2019 • Subscriber-only

Composable State Management: State Pullbacks

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.

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

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

  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.

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

Chapters
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