Maybe Monad

Rob Hilgefort
Definition
"A monad is a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic."
A way to wrap a value into a context, and a way to unwrap the value from the context:

What are we Solving?
- Eliminating distributed error checking
- SRP (Single Responsibility Principal)
- Linear control flow
const USERS = [
{ id: 1, friendId: 2, first: 'John', last: 'Smith' },
{ id: 2, friendId: 3, first: 'Rob', last: 'Hilgefort' },
{ id: 3, friendId: null, first: 'Jane', last: 'Doe' },
]
// findUser :: UserID -> User | undefined
const findUser = (userId) => USERS.find(({ id }) => id === userId)
// getFriend :: User -> User | undefined
const getFriend = ({ friendId }) => findUser(friendId)
// getFullName :: User -> String
const getFullName = ({ first, last }) => {
if (first !== null && first !== undefined && last !== null && last !== undefined) {
return `${first} ${last}`
} else {
return null
}
}
// getUserFriendName :: UserID -> String
const getUserFriendName = (userId) => {
if (userInput !== null && userInput !== undefined) {
const user = findUser(userId)
if (user !== null && user !== undefined) {
const friend = getFriend(user.friendId)
if (friend !== null && friend !== undefined) {
const fullname = getFullName(friend)
} else {
throw new Error('Cannot fetch user friend name')
}
} else {
throw new Error('Cannot fetch user friend name')
}
} else {
throw new Error('Cannot fetch user friend name')
}
}Monad Characteristics
Monads are, and depend on:
- Functor
- Apply
- Applicative
- Chain

Container
- Think of this as a new data type
- Holds data and that's it
- ... not useful ... yet.
const log = console.log
const Container = (x) => ({
cata: fn => fn(x),
})
log(Container(3))
// Container(3)
log(Container('hotdogs'))
// Container("hotdogs")
log(Container(Container({ name: 'yoda' })))
// Container(Container({ name: 'yoda' }))
Container('foo').cata(x => log(x))
// 'foo'Functor
- A container like before
- Implements `map`... that's it!
- Think of it like an `Array` with one element in it.
- This is the `Identity` Monad (incomplete implementation)
- Still, only mildly useful ...
const Container = (x) => ({
map: fn => Container(fn(x)),
cata: fn => fn(x),
})
Container(3) // Container(3)
Container(3)
.map(x => x * 2) // Container(6)
.map(x => x * 10) // Container(16)
.cata(x => x) // 16Maybe Monad
- Still just a container.
- Actually just a concept- encapsulates `Just` and `Nothing` data types.
- No more `null` checks everywhere! Only when you want to see what it is!
- Linear control flow.
- (still incomplete implementation)
const Just = (x) => ({
map: fn => Just(fn(x)),
cata: (whenJust, whenNothing) => whenJust(x),
})
const Nothing = () => ({
map: () => Nothing,
cata: (whenJust, whenNothing) => whenNothing(),
})
// MaybeOf :: Any A -> Just A | Nothing
const MaybeOf = x => (x === null || x === undefined)
? Nothing()
: Just(x)
MaybeOf(null) // Nothing
MaybeOf(undefined) // Nothing
MaybeOf('foo') // Just('foo')
MaybeOf('foo')
.map(x => x.toUpper()) // Just('FOO')
.map(x => x.split('')) // Just(['F', 'O', 'O'])
.map(x => x.length()) // Just(3)
.cata(x => x, () => throw new Error('danger!'))
// 16
MaybeOf(null)
.map(x => x.toUpper()) // Nothing
.map(x => x.split('')) // Nothing
.map(x => x.length()) // Nothing
.cata(x => x, () => throw new Error('danger!'))
// Error('danger!')Flatmap / Chain
- Flatmap or Chain
- Unwraps nested Monads
- (still incomplete implementation, missing `ap`)
const Just = (x) => ({
map: fn => Just(fn(x)),
chain: fn => fn(x),
cata: (whenJust, whenNothing) => whenJust(x),
})
const Nothing = () => ({
map: () => Nothing(),
chain: () => Nothing(),
cata: (whenJust, whenNothing) => whenNothing(),
})
// MaybeOf :: Any A -> Just A | Nothing
const MaybeOf = x => (x === null || x === undefined)
? Nothing()
: Just(x)
// find :: (Any -> Boolean) -> Array<A> -> Maybe<A>
const find = fn => arr => MaybeOf(arr.find(fn))
// upperLetterInString('b', 'rob')
const upperLetterInString = letter => data =>
MaybeOf(data)
.map(x => x.split('')) // Just(['r', 'o', 'b'])
.map(find(x => x === letter)) // Just(Just('b'))
// ???
// upperLetterInString('b', 'rob')
const upperLetterInString = letter => data =>
MaybeOf(data)
.map(x => x.split('')) // Just(['r', 'o', 'b'])
.chain(find(x => x === letter)) // Just('b')
.map(x => x.toUpper()) // Just('B')
.cata(x => x, () => throw new Error('danger!'))
// 'B'
Look Familiar?
- You probably already use Monads
- JS Promises are unlawful Monads.
- then === mapOrChain
const fetchUser = (userId) => new Promise((success, error) =>
userId === undefined
? error('no user')
: success({
userId,
friend: 5,
name: 'someone' ,
})
)
const transform = user => ({
...user,
name: user.name.toUpper(),
})
const fetchAndTransformUser = (userId) =>
fetchUser(userId) // Promise(User)
.then(transform) // Promise(User)
.then(user => fetchUser(user.friend)) // Promise(User)
// ^ should be Promise(Promise(User)) if lawful
.then(transform) // Proimse(User)
Resources
- Mostly Adequate Guide
- Pratica
- Fantasy Land
Maybe Monad
By rjhilgefort
Maybe Monad
- 47