Before I get into the main point of this article, which is to showcase the amazing Maybe monad. I would like cover a high level functional programming concept, composition. Composition is at the root of functional programming.
#Composition
const compose = f => g => x => f(g(x))
Composition allows us to compose functions together.
// Add 10%
const addFederalTax = x => x * 1.1
// Add 15%
const addStateTax = x => x * 1.15
// Traditional
// const addTaxes = price => compose(addStateTax)(addFederalTax)(price)
// Point Free
const addTaxes = compose(addStateTax)(addFederalTax)
// Lets add State and Federal taxes to 5 Dollars
addTaxes(5.00) // 6.32
Another cool concept being used above is point free style.
Great.
We will use the Maybe Monad in traversing some data, and then outputting safe to use data.
My love for McDonald's has inspired me to create a data set that represents a slice of some McDonald's restaurant's breakfast menu.
const restaurant = {
name: 'McDonalds',
location: 'Kansas City',
menu : {
breakfast : [
{name: 'Pancakes', options: ['Bacon', 'Sausage']},
{name: 'McMuffin', options: ['Egg', 'Cheese', 'Sausage', 'Ham', 'Bacon']},
{name: 'Coffee', sizes: ['Small', 'Medium', 'Large', 'X-Large'], options: ['Milk', 'Creme', 'Sugar']},
]
}
}
Maybes are great when working with data structures that are not reliable.
For example in our breakfast items example above, Coffee is the only option that includes sizes. Checking for sizes in Pancakes or Muffins would cause a runtime error. Not to mention that some locations may not even offer breakfast!
First, we want to make sure that breakfast is offered.
Before we get into the functional stuff.
Let's look at the imperative way, or more conventional style of checking if breakfast is offered.
function hasBreakfastMenu (restaurant){
if(restaurant.menu && restaurant.menu.breakfast){
return restaurant.menu.breakfast
} else {
// Do Something
console.log('Not found')
}
}
const breakfastMenuItems = hasBreakfastMenu(restaurant)
Now, we will do the same in a functional style.
To achieve this we will use get from the Pratica library. The Pratica get function returns a Monad. Monads are safe, and protect against runtime errors.
// breakfastMenu.js
import { Maybe, get } from 'pratica'
const hasBreakfastMenu = get(['menu', 'breakfast'])
hasBreakfastMenu(restaurant).cata({
Just: breakfastMenuItems => breakfastMenuItems,
Nothing: () => console.log('Not found'),
})
Great. Pretty simple? Right?
Check out the code below.
// breakfastMenu.js
import { Maybe, get } from 'pratica'
const hasBreakfastMenu = get(['menu', 'breakfast'])
/**
* hasSizes & hasOptions return us a Monad.
* In this exampe we will see how Moands can be implemented in our UI.
* Using Monads will free us from using if / else statements in our UI Components.
* */
const hasSizes = sizes => Maybe(sizes).chain(sizes => get(['sizes'])(sizes)) // Returns a Monad
const hasOptions = options => Maybe(options).chain(options => get(['options'])(options)) // Returns a Monad
const safeBreakfastMenuItems = breakfastMenuItems => breakfastMenuItems.map(
items => items.map(item => ({
...item,
sizes: hasSizes(item), // Returns a Monad
options: hasOptions(item) // Returns a Monad
})
)
)
// Entry point
export const breakfastMenuItems = compose(safeBreakfastMenuItems)(hasBreakfastMenu)
Lets break this up into 3 sections.
First, let's focus on export const breakfastMenuItems
. This is our entry point function that implements a compose and some neat point free syntax. We are composing 2 functions that return us a safe data set that we can use in a UI component. As you can see, there is no if or else, no mutability and no variable assignment.
Secondly hasBreakfastMenu
uses get
to check for the presense of menu.breakfast
. Get returns us a Maybe Monad. If menu or breakfast are not found the result will be Maybe.Nothing
. The rest of the code execution will not fire.
Finally, safeBreakfastMenuItems
the purpose of this code is to check for 2 fields sizes
and options
, which may be null or undefined. We wrap the fields in a Maybe so we can check the results in a safe way without any unexpected side-effects.
Now, I will show how we can use the output of the above code in a React UI Component.
import { React } from 'react'
import Options from './Options'
import Sizes from './Sizes'
import { breakfastMenuItems } from './breakfastMenu'
import restaurant from './restaurant' // Restaurant object data found above.
/**
* This is not necessarily how I would go about calling this function...
* It probably belongs in a reducer. But I think it is important to show how we get the result.
* */
const breakfastMenu = breakfastMenuItems(restaurant)
const MenuItem = ({ item }) =>
<div>
<h1>item.name</h1>
// Here we avoid using `if else`, instead we unwrap our Monad
{item.options.cata({
Just: options => <Options options={optons}/>,
Nothing: () => false
})}
// Here we avoid using `if else`, instead we unwrap our Monad
{item.sizes.cata({
Just: sizes => <Sizes sizes={sizes}/>,
Nothing: () => false
})}
</div>
const MenuItems = ({ breakfastMenu }) => breakfastMenu.cata({
Just : items => items.map(item => <MenuItem item={item}/>),
Nothing : () => <div>No breakfast menu offered</div>,
})
const App = () => <div> <MenuItems breakfastMenu={breakfastMenu} /> </div>
So what are some take aways that I would like to pass on here.
- Composition
- No use of if / else.
- No imperative code.
- Monads Monads Monads.
Check out Pratica! It's neat!
Top comments (2)
Great article! Very informative and love the mcdonalds references 💯🍔
I dont know my way around monads yet. Can someone explain to me why sizes are wrapped into a Maybe before using get in 'hasSizes'? Seems redundant to me as get should already put it into a Maybe?