Over this series we’ve been looking at how we would write a library which mimics the functionality of Redux from scratch to understand how it works at the most basic of levels. What we’ve seen is that there’s three basic components, a Store which is our central point, Actions which indicate something happening and Reducers which handle something happening.
To illustrate this I decided to implement Redux, well, something that does some of what Redux does, in F#. The reason I chose F# was because as we’ve seen Redux lends itself nicely to functional programming concepts, like that Reducers should be pure functions and that state is immutable, so doing it in a functional programming language seemed to fit nicely (and also it meant I could write more F# :P).
If you’re the kind of person who wants to skip ahead to the end you’ll find the code on my GitHub, but for the rest of us I’ll walk through some of the core concepts. Also I’ll point out that this isn’t the first implementation of Redux in .NET, nor is mine production ready!
Starting from the top
When implementing something like Redux in F#, or any strongly typed language, we’ll have a few things we need to be mindful of up front, one of those is that we actually have a type system, so I’m going to start by defining two types, one for our Store and one for our Middleware Store (which you’ll remember was introduced when we added middleware):
Now you’ll see here that we’ve got two generic arguments to these types, we have one which represents the
state of the application and the other is
payload, which is provided to the
Creating the Store
When creating a store you can provide up to three arguments:
- The root reducer
- The initial state
createStore functions (without using different names), so we could go with the
Option type for them, or we could make them mandatory. I’ve gone with the final approach, making them mandatory, as this will simplify some of our internal code and avoids the pain that can be introduced with dynamic typing.
So what does
createStore look like?
Oh yeah, dem type annotations! The type annotations for the
middlewares argument really melted my brain…
Now you’ll also notice that I’ve defined this as a recursive function (the
rec keyword), and that’s because we’ll need it for handling middleware, but let’s start fleshing it out:
Pattern matching FTW! We can use pattern matching to decide if we’re going to have middleware or not, and then that results in which branch to take. Let’s start making some middleware.
Implementing a store with middleware
Here’s what it looks like:
Well that’s… interesting? if you remember how we implemented middleware some of the ideas will look familiar, but we’ll break it down a little bit. First off we’ve got the
compose function which:
- Grabs the last item in the sequence
- Grabs the rest of the items
List.foldBackwhich is the same as
Array.prototype.reduceRightto walk backwards through the sequence and create a new
Again this broke my brain as I tried to try and work out how it’d come together and while strong typing helps it’s still a bit frustrating. Ultimately the way that it works is:
- Creates a function that takes 2 arguments,
funcis the current item in the collection
composedis the item that we’re returning
List.foldBack then takes 2 arguments, the collection that we’re folding and the initial value (
Ok, maybe I should have named things a bit better… but at the end of the day
compose has a signature of
compose:seq<('a -> 'a)> -> ('a -> 'a) which is a function taking a sequence of functions that return their argument and returns a single function that takes an argument and returns it. Simple!
Now we can use that inside our
createStore method. The first line of the
match when we have middleware is:
This is why we made a recursive function, when we have middleware we create a store without middleware by invoking ourself, and that’s because we’re going to grab the
dispatch method off the store, wrap it with middleware and return a new store with the new dispatch method.
Our local variable for
dispatch is defined as a mutable variable because we want to replace it with the new composed chain!
Actually making a store
Alright, the hard stuff is out of the way, now we’ll, you know, create the store!
Here’s the definition of our three core functions,
getState. None of these methods are particularly complex, the only thing we have to do that isn’t something I’m super keen on is the use of
mutable for the
subs. This is because we need to change a single point of the state, which is “owned” by the store, and we need to be able to add/remove subscribers.
At the end of the function we create a new store instance with out functions all nice and bound!
Using our Reducks store
Right so we’re all ready to go with out implementation, seriously, 53 lines of F# is all it took! Now we want to see about how we would use it in an application.
For that I’ve created a simple demo app that is a chat application. It’s a SingalR server (running in a console application) that has a Reducks store on the server that has a single method dispatch. Here’s what the hub looks like:
When the client connects the hub finds the
store and subscribes to it. When store state changes it invokes the
subscribe method on the connected client, sending the state, and the client decides what to do with the updated state.
type property of the Action we can rely on the static type to give us this, and our Action, which looks like:
Is a single type that represents a bunch of different Actions, which then can be combined with pattern matching to dispatch a store-level action (sure we could pass the action directly from the client to the server store, but I wanted to do some data remapping, basically the server becauses an action creator).
Server side implementation
So what does the server side usage of reducks look like? Let’s start with our actions:
Pretty simple looking little action creators which takes some information and creates a new value from them using the union type.
Next up let’s look at how we create the store instance:
More types, yay! I’ve a basic reducer which again leverages pattern matching to then update the appropriate part of the state. We’re able to create a new state using the existing state object but we provide a partial state, the stuff we’re changing, so that the new state is a combination of the current application and the result of the reducer. This is similar to either using
For demo purposes I’ve also created a piece of middleware which logs to the console window when the action is a
NewUser, otherwise it’s a no-op.
Finally we create the store providing the reducer, initial state (a record type) and the middleware sequence.
So fire up the console application and it’s running!
The code is on my GitHub if you’re keen to have a poke and a play (or if you want to show me how to do the F# better!), but overall I’m pretty happy with how it came together and how clean it is to read.
Some fun side effects of having the store living server side is that when a new client connects they are provided with all the existing state (assuming they dispatch a “new user” event), which can be a fun part for time travel debugging ;).