This is the fourth part of a series of posts where I show you how to use functional programming techniques to improve every day code. If you want to follow along I suggest starting from the beginning:
In the last post we used a Box to build up some computations over values. I demonstrated some basic methods for combining boxes together. And we learned how to extend our box to contain the notion of an optional value.
In this post we are going to extend the concept to a more in-depth practical example using the Inferno library. It’s just the library I’ve been using lately. Since it is meant to be almost API-compatible with React you should have little trouble translating the examples there too.
A Light Refresher on Components
This section is for those who need a quick refresher or want to know what components are. They just map some data to a JSX fragment. Unfortunately most implementations of this concept, starting with React, have two primary ways to create components: functional components and stateful components. Since we are functional programmers and because they’re easier to understand we will say component and know that we mean functional component.
So what does a component look like?
const App = props => <h1>{ props.message }</h1>
That’s it. It’s a function from an object to JSX. We know functions! We know we can compose them together. And we know functions are values and that boxes (cough, monads, cough) can nest computations together. If the theory holds then we should be able to put a component inside a box and all of the same laws should hold!
If you want to follow along in Inferno use the handy-dandy create-inferno-app script. After it has run delete all of the started files in the src/
directory save for index.js
. We’ll put all of our code in there. If everything worked you should be able to yarn run start
and see a blank page.
But before we get too excited let’s just make sure we can put our component onto the screen somewhere and see our fine message
import { render } from 'inferno'
const App = props => <h1>{ props.message }</h1>
render(<App message="Hello, world! />, document.getElementById('app'))
Beautiful!
Wrapping Components in View
Do you recall how Box works?
const Box = x => ({
x,
map: f => Box(f(x)),
inspect: () => `Box(${x})`
})
This was enough to let us .map
over our value as much as we wanted. We like doing that in Javascript!
const b = Box(5)
.map(x => x * 5)
.map(x => x - 2)
.map(x => `x is ${x}`)
With a few extra methods we implemented an option
type that let us nest computations over a value that may or not be there. We never had to test for the existence or presence of a value. We just happily .map
’d away and let the type figure it out.
Well it turns out we can create new monads to wrap different types together. Today we’re going to implement View
which is going to wrap our components. This will allow us to nest together computations over our components. And as we learn about more powerful monads we’ll see that we can do all sorts of things using this pattern. We can manage state and kick off side-effects all without sacrificing the simplicity, and purity of our code.
(As an aside, the best part in my opinion, is that you only have to learn these techniques once. In Javascript you might learn about Higher Order Components today only to have that replaced with the pattern-du-jour tomorrow. Monads are maths and that stuff sticks around regardless of language or trends)
Without further deliberation
const View = component => ({
component,
map: f => View(props => f(component(props))),
fold: props => component(props)
})
Which lets us do our favourite thing again
const App = View(props => <h1>{ props.message }</h1>)
.map(x => <span style={ props.color }>{ x }</span>)
render(App.fold({message: 'Hello, world!', color: 'red'}),
document.getElementbyid('app'))
We could do this all day
const toUpper = component => <span style=\{\{ textTransform: 'uppercase' \}\}>{ component }</span>
const withEmphasis = component => <em>{ component }</em>
const newApp = View(props => <h1>{ props.message }</h1>)
.map(toUpper)
.map(withEmphasis)
render(newApp.fold({message: 'hello, world!'}), document.getElementById('app'))
We can also chain together components by implementing chain
on our View
const View = component => ({
component,
map: f => View(props => f(component(props))),
chain: other => View(props => (
<div>
component(props)
other.fold(props)
</div>
))
Which lets us now make two small, simple, independent components and combine them into a more useful one
const emphaticMessage = View(props => <h1>{ props.message }</h1>)
.map(toUpper)
.map(withEmphasis)
const normalMessage = View(props => <p>{ props.message }</p>)
render(emphaticMessage.chain(normalMessage).fold({
message: 'I love functional programming'
}), document.getElementById('app'))
To really round this out we need to implement the Apply
and Applicative
specifications for our type
const View = component => ({
component,
map: f => View(props => f(component(props))),
chain: other => View(props => (
<div>
component(props)
other.fold(props)
</div>
)),
ap: other => View(props => other.map(component(props))),
of: component => View(component)
})
And we could add some more methods to make working with components easier. Check out Jack Hsu’s post for more information. Follow along his series to learn more about using other monad types with this basic pattern to handle state.
Conclusion
Monads are useful. They’re more powerful than design patterns. They describe a sound way to combine nested computations together. This is something we do with components. Instead of inventing a new language and new concepts to learn invest the time to understand these universal abstractions. They will serve you well long into your career regardless of which language you may be using in the future.
A good reference resource, especially if you’re coming from Javascript, is the Fantasy Land Specification.
If you’re new to these concepts I cannot recommend Professor Frisby’s Course enough. It was a big inspiration for this series of posts. Brian Lonsdorf deserves much of the credit for coming up with this brilliant way to introduce these concepts and techniques.
If you have any corrections for me or just want to say, “Hello,” drop me an email! Have fun and happy hacking.