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.
Subscribe to Point-Free
Access this episode, plus all past and future episodes when you become a subscriber.
Already a subscriber? Log in
Exercises
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) } ) } }
Update the
FavoritePrimes
view to work with aStore
that concentrates entirely on the state it cares about and nothing more.When instantiating this updated
FavoritePrimes
view, use theview
method onStore
to focus in on this concentrated state.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
, andfileprivate
access control isolate code? What are the benefits of each scope? How does this isolation differ from module boundaries?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 convertingPrimeModalState
to a protocol that exposes only the fields ofAppState
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, 1989A 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, 2018A Swift Evolution proposal for the Swift Package Manager to support resources.
Access Control
AppleThis 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, 2017A 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”.