EP 76 · Effectful State Management · Oct 14, 2019 ·Members

Video #76: Effectful State Management: Synchronous Effects

smart_display

Loading stream…

Video #76: Effectful State Management: Synchronous Effects

Episode: Video #76 Date: Oct 14, 2019 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep76-effectful-state-management-synchronous-effects

Episode thumbnail

Description

Side effects are one of the biggest sources of complexity in any application. It’s time to figure out how to model effects in our architecture. We begin by adding a few new side effects, and then showing how synchronous effects can be handled by altering the signature of our reducers.

Video

Cloudflare Stream video ID: da3d97269255a27538b21abf611c200e Local file: video_76_effectful-state-management-synchronous-effects.mp4 *(download with --video 76)*

References

Transcript

0:05

We’re now in the final stretches of defining our application architecture. We’ve solved three of the five problems that pop up in any moderately complex application, including how the entirety of app state should be modeled in a simple value type, how this state should be mutated using simple composable functions called reducers, and how an application can be decomposed into smaller pieces that can live in their own modules and be understood in isolation.

0:30

It’s now time to solve one of the biggest problems we face when building applications: side effects. We’ve talked about side effects a bunch on Point-Free. In fact, our very second episode was dedicated to identifying side effects, understanding how they can impact function composition, and recovering composition by pushing them to the boundaries of the function. We’ve also spent several episodes on dependency injection to show that it’s completely possible to control side effects in a lightweight way using what we called the “Environment”, and how this unlocks our ability to easily simulate and test certain states in our applications that would have otherwise been very difficult.

0:52

We want to understand how to model side effects in this reducer-based architecture. There are many approaches to this problem, all with their own trade-offs, but as with everything we do on Point-Free, we want our solution to be transformable and composable.

1:09

So, we are going to identify a few flavors of side-effects in our application, and then slowly step through how we can isolate and control the effects. Let’s start by recalling what application we are building… Adding some simple side effects

1:19

The app we’ve been building is a counting app with some bells and whistles. You can increment and decrement a current count, ask if it’s a prime number, and if it is, you can add or remove it from a list of favorite primes that persists across screens.

1:29

Our app can also calculate the “nth” prime, where “n” is the current count. And in order to do so, it makes an API request to Wolfram Alpha, a powerful computing platform: func nthPrime( _ n: Int, callback: @escaping (Int?) -> Void ) -> Void { wolframAlpha(query: "prime \(n)") { result in callback( result .flatMap { $0.queryresult .pods .first(where: { $0.primary == .some(true) })? .subpods .first? .plaintext } .flatMap(Int.init) ) } }

1:49

Here’s the function that executes our side effect. It should be clear that this is a side effect because it’s a function with a Void return type. Such functions have no choice but to do side effects because they don’t return anything of importance to the caller.

2:03

This network request is the sole side effect in our app so far, but it’s a complicated, asynchronous one. And although network requests are probably one of the most important side effects we can consider in an application, it’s a little too advanced for us to tackle right away. We need to start with something simpler.

2:19

So what we are going to do is add a new feature to our application that uses a side effect, one that is simpler than a network request, and that will allow us to explore the problem space of effects so that we can build up from there. While our architecture supports persisting a list of favorite primes over multiple screens, it does not support persisting this list over multiple app launches. Our app state currently only lives in memory, so every time our app launches we lose all of our user’s previously-saved favorites.

2:49

Let’s introduce a button that, when tapped, saves the current list of favorite primes to disk, and a button that, when tapped, loads it from disk and puts it into our state. While it might be a nicer experience for our users to automatically persist and load their favorite primes, let’s start with something a bit more explicit so that we can get a handle on things. We’ll first add a “Save to disk” button to the favorite primes view that, when tapped, will write all of the current favorite primes to disk.

3:13

We can add this button to the navigation bar by using the navigationBarItems view modifier, which takes either a leading or trailing view. .navigationBarTitle("Favorite primes") .navigationBarItems( trailing: Button("Save") {} )

3:32

In this button’s action closure we want to do all of the work necessary to save the favorite primes to disk. We can start by encoding state to some serializable data using a JSON encoder. .navigationBarItems( trailing: Button("Save") { let data = try! JSONEncoder().encode(self.store.value) } )

3:58

And we can write this data to the app’s documents directory. .navigationBarItems( trailing: Button("Save") { let data = try! JSONEncoder().encode(self.store.value) let documentsPath = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true )[0] let documentsUrl = URL(fileURLWithPath: documentsPath) let favoritePrimesUrl = documentsUrl .appendingPathComponent("favorite-primes.json") try! data.write(to: favoritePrimesUrl) } )

5:04

We can also add a button for loading the previously saved favorite primes. We’ll start by throwing the buttons into an HStack so that they lay out next to each other in the navigation bar: .navigationBarItems( trailing: HStack { Button("Save") { … } Button("Load") { } } )

5:20

To load from the disk we simply need to load the data off disk and try to deserialize it into an array of integers: Button("Load") { let documentsPath = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true )[0] let documentsUrl = URL(fileURLWithPath: documentsPath) let favoritePrimesUrl = documentsUrl .appendingPathComponent("favorite-primes.json") guard let data = try? Data(contentsOf: favoritePrimesUrl), let favoritePrimes = try? JSONDecoder() .decode([Int].self, from: data) else { return } <#???#> }

6:04

Now at this point we have our loaded primes, but we don’t have a way of getting those primes back into the app state. Remember, the value field on our store, which holds the state for the screen, has a private setter, which means we couldn’t mutate it even if we wanted to: self.store.value = favoritePrimes Cannot assign to property: ‘value’ setter is inaccessible

6:15

Luckily, we don’t want to do this, because as we have seen over the many past weeks, in this architecture it is beneficial to send all user actions directly to the store, and allow the reducer to do this work for us.

6:30

In order to do that we need to introduce a new action to our FavoritePrimesAction enum: public enum FavoritePrimesAction { case deleteFavoritePrimes(IndexSet) case loadedFavoritePrimes([Int]) }

6:42

We need to implement this action in the reducer: case let .loadedFavoritePrimes(favoritePrimes): state = favoritePrimes

6:53

And we can now send that event once the data is loaded from disk: self.store.send(.loadedFavoritePrimes(favoritePrimes))

7:05

While the favorite primes module is now in building order, our app isn’t quite there yet, because we need to account for this new action over in our activityFeed higher-order reducer, which was responsible for inspecting every action that goes into the system so that we could keep track of an activity feed.

7:38

This is actually a great compiler error to have. It’s forcing us to recognize that there is a new user action in the application, and we get the opportunity to decide if it affects our activity feed or not. This is the power of being able to globally monitor all actions coming into the system, and it’s awesome that it is so simple to add cross-cutting features like this.

7:56

With all that said, this action isn’t important for our activity feed, so we are going to let it go by without doing anything: case .counter, .favoritePrimes(.loadedFavoritePrimes): break

8:08

Things are building, and if we run the app the feature will work as expected. We can add a few favorite primes, go back to our favorite primes list and save it. And then if we relaunch the app and tap load we will get all of those primes back. Effects in reducers

8:35

We’ve now introduced a couple of simple side effects to our application, and we’ve shown how they can feed data back into our application’s state by introducing a new action.

8:45

However, everything we just did goes completely against the grain of our architecture. So far we have strived to make the action closures of our UI elements as logic-less as possible. Ideally all they do is package up some data into an action enum case, and then send that data off to the store so that reducer can handle all of the logic. But here we are doing a bunch of work to load and save data to the disk.

9:13

We want to find a way to capture all of this work in our reducers so that our view doesn’t care about it.

9:24

Let’s start with the save action. Instead of performing all this work in line, it would be nice to send an action to the store and do no other work in the button closure. .navigationBarItems( trailing: HStack { Button("Save to disk") { self.store.send(.saveButtonTapped)

9:53

In order to make this work we need to introduce a new action and see what that forces us to consider: public enum FavoritePrimesAction { case deleteFavoritePrimes(IndexSet) case loadedFavoritePrimes([Int]) case saveButtonTapped }

10:01

This breaks the favorite primes reducer. public func favoritePrimesReducer( state: inout [Int], action: FavoritePrimesAction ) { switch action { Switch must be exhaustive We can switch on the new case. case .saveButtonTapped:

10:09

What work goes in here? Well, let’s take the work we were doing in the button and replace self.store.value with state . case .saveButtonTapped: let data = try! JSONEncoder().encode(state) let documentsPath = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true )[0] let documentsUrl = URL(fileURLWithPath: documentsPath) try! data.write( to: documentsUrl.appendingPathComponent("favorite-primes.json") )

10:26

The compiler seems happy with this change, but if we build the app, we get another error in our activity feed higher-order reducer. func activityFeed( _ reducer: @escaping (inout AppState, AppAction) -> Void ) -> (inout AppState, AppAction) -> Void { return { state, action in switch action { Switch must be exhaustive

10:40

While it’s nice to have the compiler force us to consider each case, it’s getting in the way of some more rapid feedback, so let’s switch over to build just the favorite primes module, which can be run in a playground we created last time .

11:01

Here we can test that saving still works, just as it did before, even though we moved that code to the reducer. Reducers as pure functions

11:25

But what have we done to accomplish this!? Up till now our reducers have been what are called “pure” functions, which is to say that everything they do is captured in their signature: they take in some state and an action as input, and decide how they want to change state given that action. They are fully determined by the data they were given.

11:43

On the other hand, so-called “impure” functions are ones that either need access to things in the outside world that were not passed in as an argument or they need to make changes to the outside world that were not described as outputs of the function. This means that side-effects are nothing more than hidden inputs or outputs of a function, things that the function are either implicitly depending on or implicitly changing. And as we probably all know, implicitness does not create a welcoming environment in codebases.

12:14

With the changes we just made to our favorite primes reducer, it is no longer pure. It is performing a side effect directly in the reducer by saving data to the outside world (in this case to the disk), which means there’s a hidden output of this function lurking in the shadows. That makes this reducer much harder to understand at a glance, and more difficult to test.

12:28

We discussed the idea of side-effects being hidden inputs and outputs in just our second episode on Point-Free, entitled “ Side Effects .” We described one way to wrangle in this problem is to push the side effect to the boundary. That is, rather than performing the effect directly in the function, you can instead describe it in a value and return it as a new output of the function, thus passing the responsibility of execution to the caller.

12:50

For example, in the episode we considered this simple function that hid a side effect in its body: func compute(_ x: Int) -> Int { let computation = x * x + 1 print("Computed \(computation)") return computation }

13:02

The side effect was printing to the console, which is innocuous enough, but something completely untestable and unreflected in the compute function’s signature.

13:20

We saw, though, that we could slightly augment the signature of compute to describe the effect of printing to console rather than executing the effect itself. func computeAndPrint(_ x: Int) -> (Int, [String]) { let computation = x * x + 1 return (computation, ["Computed \(computation)"]) }

13:43

And now it’s up to the caller to perform the side effect of printing. Now printing is not the greatest example of a side effect since people usually do want to sprinkle these throughout their code for debugging. But imagine instead of printing we were tracking an analytics event. Then we have a very important piece of business logic firing off into the void with no easy way to test it.

14:11

Turns out, we can apply this strategy of extracting out a side-effect to our reducers. Right now, the reducer signature is as follows: (inout Value, Action) -> Void

14:35

We’ll be making lots of changes to this function signature in the next few episodes as we experiment with different models of effects. So, it might be a good idea to introduce a type alias that describes what a reducer signature is. We can start with the current, effect-less state of affairs: public typealias Reducer<Value, Action> = (inout Value, Action) -> Void

15:01

And we can update the signatures of our composable architecture to work with this type alias directly, including the store’s private reducer property: public final class Store<Value, Action>: ObservableObject { private let reducer: Reducer<Value, Action>

15:09

The store’s initializer: public init( initialValue: Value, reducer: @escaping Reducer<Value, Action> ) {

15:15

And the higher-order reducers, like combine , which combines multiple reducers together: public func combine<Value, Action>( _ reducers: Reducer<Value, Action>... ) -> Reducer<Value, Action> {

15:21

And pullback , which transforms reducers that work on local state into reducers that can work on more global state by describing how to pluck out local state and local actions from global state and global actions using key paths: public func pullback< LocalValue, GlobalValue, LocalAction, GlobalAction >( _ reducer: @escaping Reducer<LocalValue, LocalAction>, value: WritableKeyPath<GlobalValue, LocalValue>, action: WritableKeyPath<GlobalAction, LocalAction?> ) -> Reducer<GlobalValue, GlobalAction> {

15:44

And finally, the higher-order logging reducer, which adds console logging to any reducer: public func logging<Value, Action>( _ reducer: @escaping Reducer<Value, Action> ) -> Reducer<Value, Action> {

15:51

Everything still builds, but now we can focus on the reducer’s signature in a single place where we determine what needs to change. public typealias Reducer<Value, Action> = (inout Value, Action) -> Void Effects as values

16:01

Alright, we’re now primed to update the reducer signature in a single place, so what are we going to change? Well, rather than simply return Void we could return a description of the side effect. But what kind of value could describe the side effect of writing to disk?

16:10

Well, we were originally performing the side effect directly in the body of the button, which has the following signature. Button.init(<#title: StringProtocol#>, action: <#() -> Void#>)

16:18

Initializing this button does not perform the given action. It is only when the button is tapped that the action is evaluated. And action is merely a void-to-void closure, which seems as good of a definition as any for how to describe a side effect in code.

16:41

We’ll use a type alias for now to express the shape our effect. public typealias Effect = () -> Void

16:59

With this, we could change our reducer signature to describe effects by returning these closures, instead. public typealias Reducer<Value, Action> = (inout Value, Action) -> Effect Updating our architecture for effects

17:14

With this signature, we can allow our reducers to do any state changes they need to on the inside, while giving them a way to package up their effects into a closure to be executed later. This makes it easier for our reducers to stay pure.

17:35

Unfortunately, this change has broken everything about how are existing architecture worked, so let’s go through all the errors to get things working again.

17:46

The first compiler error is in the send method. public func send(_ action: Action) { self.reducer(&self.value, action) Expression resolves to an unused function

17:51

Interestingly, it’s a Swift compiler error to have an unused function in a statement, whereas typically an unused value is only a warning. This could be to help people out who might be more familiar with languages that allow you to drop parens from function invocations: [1, 2].dropFirst Expression resolves to an unused function

18:16

Either way, the compiler is now forcing us to handle the effect returned to us from the reducer. So let’s capture it in a variable: let effect = self.reducer(&self.value, action)

18:23

And all the store has to do is run the effect. let effect = self.reducer(&self.value, action) effect()

18:30

The combine function takes a bunch of reducers as input and returns a brand new reducer that simply calls all of the given reducers. But now each reducer returns an effect closure, and the compiler is letting us know that we’re not handling any of them. public func combine<Value, Action>( _ reducers: Reducer<Value, Action>... ) -> Reducer<Value, Action> { return { value, action in for reducer in reducers { reducer(&value, action) Expression resolves to an unused function

18:37

Whenever we run a reducer, we need to handle the effect. let effect = reducer(&value, action)

18:40

We’re still living in the world of a reducer function here, which means we don’t want to evaluate the effect here. Instead, we need a way of tracking each reducer’s effect and then somehow combine them all into a single effect closure.

18:55

We can keep track of all of the effects over time by introducing an array. var effects: [Effect] = []

19:02

Now we can loop over our reducers, and when we call each one, we can append its effect to the array. var effects: [Effect] = [] for reducer in reducers { let effect = reducer(&value, action) effects.append(effect) }

19:10

Finally, we must return a single effect, so let’s open up a closure. var effects: [Effect] = [] for reducer in reducers { let effect = reducer(&value, action) effects.append(effect) } return { }

19:13

And in this closure we can loop over all of the effects we’ve accumulated, and run each one. var effects: [() -> Void] = [] for reducer in reducers { let effect = reducer(&value, action) effects.append(effect) } return { for effect in effects { effect() } }

19:33

We can shorten this a bit using map to build up the array of effect functions: let effects = reducers.map { $0(&value, action) } return { for effect in effects { effect() } }

19:48

So what about the pullback function? It has a couple errors. public func pullback< LocalValue, GlobalValue, LocalAction, GlobalAction >( _ reducer: @escaping Reducer<LocalValue, LocalAction>, value: WritableKeyPath<GlobalValue, LocalValue>, action: WritableKeyPath<GlobalAction, LocalAction?> ) -> Reducer<GlobalValue, GlobalAction> { return { globalValue, globalAction in guard let localAction = globalAction[keyPath: action] else { return } Non-void function should return a value

19:53

The first is on a line that tries to extract a local action from a global one. If it fails, it returns because it doesn’t have a local action it can feed the given reducer.

20:01

We now need to return an effect, and in this case we can return an empty, no-op closure. guard let localAction = globalAction[keyPath: action] else { return {} }

20:08

The other error occurs when we invoke our local reducer. reducer(&globalValue[keyPath: value], localAction) Expression resolves to an unused function

20:10

This now returns an effect. let effect = reducer(&globalValue[keyPath: value], localAction)

20:13

Which we can return immediately from the body of the reducer. return effect

20:18

All that’s left in this module is the logging higher-order reducer. We can update its signature to work with effectful reducers. public func logging<Value, Action>( _ reducer: @escaping Reducer<Value, Action> ) -> Reducer<Value, Action> { return { value, action in reducer(&value, action) Expression resolves to an unused function

20:19

And the compiler reminds us, yet again, that we need to handle the effect the reducer returns. One thing we can do is store it in a local variable so that we can return it after doing some logging. let effect = reducer(&value, action) print("Action: \(action)") print("Value:") dump(value) print("---") return effect

20:28

This builds, but we should note a problem here. The logging function is performing some side effects! It’s printing a bunch of things to the console, which may not seem like a big deal, but as we noted before, it could easily be changed to log to disk or even over the network.

20:39

Now that our reducer bakes in the notion of side effects directly into its signature, we should be able to capture these side effects in a similar fashion. Rather than print as soon as the given reducer is called, we can push that work into an effect closure. let effect = reducer(&value, action) return { print("Action: \(action)") print("Value:") dump(value) print("---") effect() } Escaping closure captures ‘inout’ parameter ‘value’

20:50

This error is actually a very good thing. It says that inout values cannot be captured by escaping closures. Remember that inout is all about super locally scoped mutation. Using inout for an argument in a function means that the function is being explicit that it wants a mutable value by annotating its argument with inout , and the caller is being explicit that it is allowing mutation by annotating the value it passes with an ampersand. You can kind of think of it as the caller and callee entering a contract with one another that is only valid at that exact instant of invocation of the function.

21:28

If it was allowed to let a mutable inout value to travel far off into an escaping closure that can be executed at anytime in the future we would break that locality since the value could be mutated at a later time.

21:40

To work around this we just need an immutable reference to this data, which is ok since we don’t plan on mutating anything. let effect = reducer(&value, action) let newValue = value return { print("Action: \(action)") print("Value:") dump(newValue) print("---") effect() }

21:51

We have one more error back in the view method, which broke because the reducer initializer needs to return an effect. public func view<LocalValue, LocalAction>( value: @escaping (Value) -> LocalValue, action: @escaping (LocalAction) -> Action ) -> Store<LocalValue, LocalAction> { return Store<LocalValue, LocalAction>( initialValue: value(self.value), reducer: { localValue, localAction in self.send(action(localAction)) localValue = value(self.value) } Missing return in a closure expected to return ‘Effect’ (aka ‘() -> ()’)

22:06

Remember that view is a method that allows us to take a store that works on some global state and global actions and have it focus in on a more local subset of state and actions. It calls send internally, which performs the root store’s effects, so we can return an empty, no-op closure here, because the act of creating a view should not introduce any new side effects. reducer: { localValue, localAction in self.send(action(localAction)) localValue = value(self.value) return {} }

22:27

We’ve now fully upgraded the Composable Architecture module to work with effectful reducers, but none of our existing app code is compatible. Let’s hop over to the favorite primes module and update its reducer’s signature. public func favoritePrimesReducer( state: inout [Int], action: FavoritePrimesAction ) -> Effect {

22:46

And then we can evaluate each case to determine if it executes a side effect or not. Deleting a favorite prime should not execute a side effect, so we can return a no-op closure. case let .deleteFavoritePrimes(indexSet): for index in indexSet { state.remove(at: index) } return {}

22:54

Replacing the favorite primes does not execute a side effect either, so we can return a no-op closure here, as well. case let .loadedFavoritePrimes(favoritePrimes): state = favoritePrimes return {}

23:00

And for the last, we can take the existing side effect that we’re performing inline and wrap it up in a closure so that it can be evaluated later by the store: case .saveButtonTapped: return { let data = try! JSONEncoder().encode(state) let documentsPath = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true )[0] let documentsUrl = URL(fileURLWithPath: documentsPath) try! data.write( to: documentsUrl.appendingPathComponent("favorite-primes.json") ) } Escaping closure captures ‘inout’ parameter ‘state’

23:14

Again we just need a local, immutable reference to the state that can be used in the effect: case .saveButtonTapped: let state = state return { let data = try! JSONEncoder().encode(state) let documentsPath = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true )[0] let documentsUrl = URL(fileURLWithPath: documentsPath) try! data.write( to: documentsUrl.appendingPathComponent("favorite-primes.json") ) }

23:21

Everything is building, we can run the favorite primes screen, and the whole feature should work exactly as it did before, though now the reducer no longer performs the side effects. Reflecting on our first effect

23:44

But we have just extracted our first effect into our architecture. Let’s reflect on what we just accomplished.

23:50

We had a side effect in our view, saving favorite primes to disk, that we knew we needed some way to control. For one thing it’s untestable code, but for another we have found that a useful way to simplify our views was to move all of their logic into our reducers, and simply make the view responsible for sending user actions to the store.

23:57

So, we naively and literally moved the side effecting code into the reducer. This technically got the job done, but that destroyed all of the nice testability and understandability of the reducer.

24:07

So, we recalled some of the lessons we learned from our previous episode on side-effects , in particular we can often push effects to the boundary of a function by introducing a new output to the function that represents the effect we want to execute without actually executing it.

24:21

This led us to change our reducer signature to be a function that returns a void-to-void closure, which can hold the side effecting work without actually executing it, which passes the responsibility of executing the work to the store.

24:33

After fixing some compiler errors we were able to finally encapsulate the saving work in a closure, and get the store to run it for us rather than doing it in the reducer. Best of all, changing the reducer signature like this didn’t prevent us from still having composable reducers and stores, which is the true power behind this type of architecture.

24:42

This is our first step to introducing effects to our architecture, and it was a simple effect. We have a few more steps to go because there are more complicated effects we need to model. For example, loading our list of favorite primes is a little different from saving. Saving is mostly a fire-and-forget operation, we just run the work and we don’t need to communicate back to the reducer that anything happened.

25:03

However, loading does some work to load data from the disk, and then somehow we want to communicate that work back to the reducer.

25:14

So let’s figure out how we need to change this effects model in order to capture that ability…next time! References Side Effects Brandon Williams & Stephen Celis • Feb 5, 2018 We first discussed side effects on the second episode of Point-Free. In that episode we showed how side effects are nothing but hidden inputs or outputs lurking in the signature of our functions. We also showed that making that implicit behavior into something explicit makes our code most understandable and testable. Side effects can’t live with ’em; can’t write a program without ’em. Let’s explore a few kinds of side effects we encounter every day, why they make code difficult to reason about and test, and how we can control them without losing composition. https://www.pointfree.co/episodes/ep2-side-effects Dependency Injection Made Easy Brandon Williams & Stephen Celis • May 21, 2018 One of the easiest ways to control side effects is through the use of “dependency injection.” In an early episode of Point-Free we showed a lightweight way to manage dependencies that gets rid of a lot of the boilerplate that is common in the Swift community. Note Today we’re going to control the world! Well, dependencies to the outside world, at least. We’ll define the “dependency injection” problem and show a lightweight solution that can be implemented in your code base with little work and no third party library. https://www.pointfree.co/episodes/ep16-dependency-injection-made-easy Redux Middleware Redux, at its core, is very simple and has no single, strong opinion on how to handle side effects. It does, however, provide a means of layering what it calls “middleware” over reducers, and this third-party extension point allows folks to adopt a variety of solutions to the side effect problem. https://redux.js.org/advanced/middleware Redux Thunk Redux Thunk is the recommended middleware for basic Redux side effects logic. Side effects are captured in “thunks” (closures) to be executed by the store. Thunks may optionally utilize a callback argument that can feed actions back to the store at a later time. https://github.com/reduxjs/redux-thunk ReSwift ReSwift is one of the earliest, most popular Redux-inspired libraries for Swift. Its design matches Redux, including its adoption of “middleware” as the primary means of introducing side effects into a reducer. https://github.com/ReSwift/ReSwift SwiftUIFlux Thomas Ricouard An early example of Redux in SwiftUI. Like ReSwift, it uses “middleware” to handle side effects. https://github.com/Dimillian/SwiftUIFlux 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. https://elm-lang.org 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 . https://redux.js.org Composable Reducers Brandon Williams • 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”. https://www.youtube.com/watch?v=QOIigosUNGU Downloads Sample code 0076-effectful-state-management-synchronous-effects Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .