19 minutes read
PureScript-Redux is a small library which helps to utilize the Redux state container with PureScript. Although I have almost no experience with React, which is the most prominent ecosystem for using Redux, I thought it would be a nice learning exercise to create a set of Redux-Bindings for writing WebApps in PureScript. Redux itself is heavily, and rightfully so, promoting the benefits of using pure functions for managing the application state and PureScript, being a Haskell-dialect, is a purely functional language. Therefore, it seemed to me very logical to try to combine them together. But because I’m also a PureScript-Beginner and still learning the language I can’t give you any warranty that this implementation follows all the proper idioms and/or best practices.
Before we start I’d like to ask others for help to make my current implementation less buggy, more versatile, and more idiomatic.
Now, let me quickly write down a few words on Redux and how I understand its mechanisms. If you find any mistakes, please, leave a comment. Thanks!
Redux – predicticting application state without drowning in Flux
After React went mainstream soon it became clear that even the fastest possible View-Engine alone won’t help you much in creating complete Web Applications. I still remember asking myself how I should combine React with my Backbone models, routers and other machinery. I was interested in React but it made a little sense to me to play with the View-Layer alone hoping the rest will ‘somehow’ fit together. Luckily, the Flux architecture came out….and created even more confusion in my (only my?) brain. It was hailed as a ‘clear‘ and ‘easy to understand‘ unidirectional architecture that will help us avoid the typical problems of MVC where you end up with Event-Spaghetti spilled all over your code and templates. Well, I still have problems to understand Flux as a whole but this isn’t that important to me today. After having given up Flux I also stopped learning React because I was already very happy with RactiveJS and AmpersandJS (a more modular version of BackboneJS). RactiveJS gives me all the power of real declarative programming combined with its super-fast Parallel DOM (roughly like React’s Virtual DOM, only faster), and numerous AmpersandJS modules offer me everything I need for my daily work and private projects.
In the mean time (2014-15) many Flux replacements appeared on the stage but if I’m not mistaken, most of them are trying to make Flux ‘easier to digest‘ without changing anything substantial at the core of its architecture. However, I’m not trying to incite any flame wars because this is a technical blog and I’ll always provide some code in my articles. Therefore I’ll stop now just as I’ve stopped learning React two years ago when it became obvious to me that the complex React/Flux-architecture was yet another path to nowhere for ordinary developers like me.
PureScript-Redux + RactiveJS
It’s not a secret that I’m a happy user of RactiveJS because it gives me everything I need from a UI-Library without forcing me to obey to the rules of [put-any-IT-ideology-here]. To make the demo more complete and a little bit more realistic we’ll combine Redux, PureScript and RactiveJS to create a Web App with a Bootstrap-based UI, some event handling, a Redux-listener and Logging-Middleware.
Everything will be written in PureScript.
The Redux API
Currently, the following Redux APIs are supported:
At the first glance this source looks like any other CommonJS module. It has an require, a few function references and, more importantly, a special comment at the beginning of the file. Every PureScript-aware module that contains foreign import definitions must have such a comment with the fully qualified module name. In our case the library is named Control.Monad.Eff.Redux. The actual file doesn’t have to have such a long name. Redux.js is sufficient enough. Its PureScript counterpart, of course, is named Redux.purs. Also, we notice the several nested function calls. This is because PureScript’s functions have only one parameter and to make both sides (JS/PS) happy we have to cascade the arguments over multiple function calls. There’s also an alternative way of doing it via certain helper libraries from PureScript that can take care of all these arguments but I’m not using them here for now. As we can see the _createStore function doesn’t do much. It basically receives both parameters (reducer, initialState) and calls the real createStore function that we’ve referenced already at the beginning of the module. Finally, we return the new store. Now, let’s quickly switch over to PureScript to learn about applying createStore from there.
As we already know the createStore-API expects us to deliver a proper Reducer-function. In our case this will be a simple counter function that takes a valid Action ‘object’ and according to the command inside action increments or decrements the current state. If we receive an invalid Action we’ll simply return the current state.
To register our Reducer we use the createStore foreign import and give it the initial state of Int 1.
Subsequently we initialize our RactiveJS application by giving it a bunch of configuration properties. I’m not discussing the creation of RactiveJS apps here because there are already a few articles on this subject available on this blog. I’m also maintaining a library with RactiveJS bindings for PureScript and I’d like to hear your opinions about it (email, comments, tweets, telepathy etc.).
What’s important in this app is that we also give it the store reference we got from the previous createStore-call. We now have our state at the center of our app and we know that there’s only one possibility to change it. With this strategy we centralize our application state. There may be other strategies too and presumably much better than this one. What’s important is that we not only logically (Redux) centralize our application’s state but physically too, by putting it in a self-contained component. And RactiveJS instances are components.
Finally, we subscribe to a few events. I’ll also avoid talking much about proxy-events under Ractive and how to declare them. There’s an article on that in the case someone is interested in this topic.
Now, after we’ve successfully built the app with the help of Gulp and WebPack we open the index.html and click the two buttons to see some output in the console:
Well, this looks ‘OK’ but somewhat different than the console output at the beginning of the article. If you go back and double-check you’ll see that here’s no middleware at work! Those of you who know Redux much better than I do would immediately recognize the difference: I wasn’t using any kind of Store Enhancer to introduce some Middleware in our Redux instance! The default enhancer for Redux, and the only one shipping with it, is the function applyMiddleware which is also available as a foreign import. Let’s see how this function works.
Enhancing the Store with Middlewares
We can extend the default functionality of Redux by wrapping it’s store’s dispatch method. Just for those of you who have never used Redux before: every Store contains an API comprising of these functions:
The dispatch function is responsible for state changes as it dispatches actions that ultimately lead to state changes. Now, if we somehow hook into this process-chain we’d get a pretty cool option to execute additional tasks before the actual state-change happens (or even influence it). And this is what applyMiddleware does for us. But first, let’s look at its implementation in Redux.js:
Note that this way of registering Middleware is only possible since v3.1.0 of Redux. Previously, Redux used a more complex logic where you’d have to create a ‘pimped’ version of createStore called createStoreWithMiddleware by using applyMiddleware in a separate call. The returned value from applyMiddleware would then be an expanded version of createStore that you’d have to feed it with the initial reducer and initial state.
- take any number of hooked-in functions which want to do something before the default dispatch method reaches the Reducer (and the state gets changed, of course)
- take care that all of them are being executed in order (that is, no multiple calls and no function left behind)
- to make them play in concert they’ll be composed from right to left (effectively, this means that every function will have a reference to the dispatch function of the next Middleware in chain)
- ultimately, we’ll create a single dispatch function based on all those Middleware-functions. This final function will call all of the specialized dispatches one after another until it reaches the original dispatcher-function and grabs the store itself.
A more detailed explanation of the inner workings of Middleware API can be found here.
At the bottom of the above image we see the definition of an middleware array and application of applyMiddleware. We also declare the counter function as our Reducer with initial state of Int 1. Above the main function is the Logging mechanism defined. Following the layout of the Middleware API it expects these references as arguments:
- Store [note: this is not a complete Store object as is contains only a subset of the original Store API]
- Next (the reference to the next dispatcher function in chain)
- Action (which will serve as the argument of the next-dispatch call)
Therefore, the result of the Logger-function application is always a next(action) application which leads to the next Middleware in chain (if there are more of them) and so on. This harmlessly looking next(action) provokes a chain reaction that at its end applies the original dispatch-function to ultimately change the application state.
Playing with the Demo App
Our application exposes two buttons that generate certain events which create Actions. I should also note that I’m not using any Action Creators here because the demo application is really small and comprising only of two Actions. There was no need to add a superfluous Action Creator that would only produce two simple JSON structures. However, when developing real applications you should consider using Action Creators or similar mechanisms or you’ll end up chasing configuration objects, their properties, unkempt constant objects etc.
Just to make it more visible where the events come from here’s a part of the application template built with Ractive’s Mustaches. Here’s more info on Proxy Events and how to build Components with RactiveJS.
Now, when we, for example, click on the Decrement-Button the following piece of logic will be executed delivering a new Action object.
We get our Store object from our App by using Ractive’s get-function. Please note that we’re not doing any kind of ‘assignments’ here. Actually weren’t doing it anywhere else in the code because PureScript doesn’t know anything about value assignments. The meaning of these arrows is quite different and has much to do with Monads but I’ll avoid any deep explanation of Monads because I’m not the right person to handle such complexities. Yes, I tried it once and I still think it was a disaster but I’m trying to remain honest and will not remove the article, so you can laugh at me.
After we’ve clicked on the button and generated the event, our dispatch will fire the Action object (internally, as shown in the picture above, the Store object will do this for us). This will ultimately lead to an internal calling of getState and returning of a new state value which will be considered as the current state. At the same time all previously registered listeners will be notified about this state change. In our case the listener is a simple logging function (not to be confused with the Logging-Middleware we saw previously!)
As we see here there’s not much logic in it. We only ask for the current State and show it on the console. It’s really important to differentiate between listeners that react to changes and middlewares which drive the change in Redux. A listener can only listen to changes but not hook into the actual state-change process. To make Redux aware of such a listener we have to use another method from its API called subscribe which, of course, is also available as a foreign import:
Redux is powerful and despite its small size not always easy to grasp. There’s a lot going on inside its sophisticated machinery and it took me quire some time to understand all its Middleware stuff. Maybe I belong to the minority that’s had hard time with understanding of Redux’ Middleware but the development of purescript-redux helped me a lot to get a better insight into Redux’ innards. I’m still not quite sure if my understanding is complete enough to justify the development of purescript-redux but because there wasn’t any other PureScript implementation I could only try it by myself. If there’s someone else interested in development of it, please, drop me an email, a comment, a tweet, or send a pull request. Thank you!