Feasgar Math!
Good afternoon!
I am thrilled to be here today.
My name is Cory and I am a senior web developer at Aumni.
cory brown
why yes, we are hiring. thank you for asking
Monads!!!
Monads are (in)famously said to be cursed.
This is has become something of a self fulfilling prophecy. Today, we are going to break that curse.
- Douglas Crockford
In less than 15 minutes, you will understand monads
A monad may encapsulate values of a particular data type, creating a new type associated with a specific additional computation
https://en.wikipedia.org/wiki/Monad_(functional_programming)
String
Monad[String]
// String
'a string'
[] // <- Box
// [String]
// AKA string in a box
['a string']
// String -> Number
const strLength = (str) =>
str.length
strLength(['a string'])
🚫
strLength(['a string'])
['a string'].map(strLength)
✅
Functor
[🤷♀️].map(strLength)
[🤷♀️]
[🏃♀️].map(strLen)
[🏃♀️]
fetch('/string')
.map(strLen)
.then(strLen)
Promise
[🤷♀️].map(strLength)
const getVal = () =>
Promise.resolve(42)
getVal().then(add(5))
[5].map((num) => num + 2)
Promise.resolve(5)
.then((val) => val + 2)
Functor
Monad(ish)
Breaking
The
Curse
What is a Monad?
(It's a mappable container for a value that encapsulates some bit of work within it.)
Could you explain a Monad to someone?
(It's a mappable container for a value that encapsulates some bit of work within it.)
Of course there's more to Monads than that.
but those other bits are not what's hard to understand about Monads.
Associativity
Left Identity
Right Identity
Lazy Evaluation
Promises are not, in fact, monads.
but they demonstrate the "weird" part of monads quite nicely.
Monads are about encapsulating a particular kind of work.
But why?
Patterns
Monads are an abstraction over a complexity of a given kind, removing the need to understand or implement that kind of complexity from the user of it.
Monads give a consistent framework for abstractions of this kind so they can interoperate with each other predictably.
We give these kinds of abstractions names -- like "functor" and "monad" -- so that we can communicate that they follow certain rules in their implementation. If an encapsulation is a Functor or a Monad, we already know many things about it.
The freedom is in the constraint.
When forced to work within a strict framework the imagination is taxed to its utmost – and will produce its richest ideas. Given total freedom the work is likely to sprawl. - TS Eliot
Give me the freedom of a tight brief - David Ogilvy
Monads are not powerful because they are flexible.
They are powerful because they are strict.
Given some type, we can construct a Monad that adheres to strict, universal rules such that it is impossible to enter an invalid state so long as we remain in the context of the Monad.
map
One thing we know about all functors, including monads, is that it has a `map` (or a map-like) method.
This method must take a function that expects a single argument of the type that our Monad contains.
We also know that calling the `map` method returns a new Monad of the same type with a new value contained in it.
MonadA(x).map(x => y) => MonadA(y)
chain
We don't always want to return a Monad of the same type after applying a function. In those cases, we can use a method called `chain` (sometimes `flatMap`) or other chain-like methods.
`chain` will simply return the result of calling the function on the contained value.
We can choose to dump out of our Monad context. 😰
Or change what Monad we wrap the new value in. 😍
MonadA(x).chain(f) => f(x)
MonadA(x).chain((x) => MonadB(x)) => MonadB(x)
& others
There are other methods on functors and monads that are useful and important. Mostly, these can be categorized in three ways.
- map-like
- chain-like
- convienience methods
There are many different types of monads.
Each type encapuslates a particular type of work
Encapsulation
for instance
Log Monad
Log<T>
The Log Monad encapsulates logging.
Logging is a side effect since it is observable outside of the function execution (it prints to the screen).
In functional programming, side effects are encapsulated in Monads like Log and others.
Log Monad
Log<T>
const Log = (value) => ({
map: (fn) => {
const val = fn(value)
console.log(`Mapped ${value} to ${val}`)
return Log(val)
},
chain: (fn) => {
const val = fn(value)
console.log(`Chained ${value} to ${val}`)
return val
},
toString: () => `Log(${value})`
})
Log Monad
Log<T>
Log(42)
.map(add(5))
.map(less(3))
.chain(mul(2))
// 'Mapped 42 to 47'
// 'Mapped 47 to 44'
// 'Chained 44 to 88'
//> 88
Maybe Monad
Maybe<T>
The Maybe monad encapsulates nullish checks (null, undefined).
Maps are skipped for nullish values and executed for non-nullish values.
Old and busted
if (obj) {
if (obj.some) {
if (obj.some.deep) {
if (obj.some.deep.property != null) {
return obj.some.deep.property
}
}
}
return null
Maybe Monad
Maybe<T>
const Just = (val) => ({
map: (fn) => Maybe(fn(val))
})
const Nothing = () => ({
map: () => Nothing()
})
const Maybe = (value) =>
value == null
? Nothing()
: Just(value)
Maybe Monad
Maybe<T>
const obj = {
some: {
deep: {
property: 5
}
}
}
Maybe(obj)
.map(getProp('some'))
.map(getProp('deep'))
.map(getProp('property'))
// Just(5)
const obj = {}
Maybe(obj)
.map(getProp('some'))
.map(getProp('deep'))
.map(getProp('property'))
// Nothing()
New Hotness
return obj?.some?.deep?.property ?? null
Maybe Monad
I'm not quite dead yet
const obj = {
some: {
deep: {
property: { url: 'http://api.aumni.fund' }
}
}
}
const head = ([head, ...tail]) => head
const tail = ([head, ...tail]) => tail
Maybe(obj)
.map((obj) => obj?.some?.deep?.property?.url)
.map(match(/^https:\/\/(.*)/))
.map(([, pathname]) => pathname)
// Just('api.aumni.fund')
Maybe Monad
I'm not quite dead yet
const obj = {
some: {
deep: {
property: null
}
}
}
const head = ([head, ...tail]) => head
const tail = ([head, ...tail]) => tail
Maybe(obj)
.map((obj) => obj?.some?.deep?.property?.url)
.map(match(/^https:\/\/(.*)/))
.map(([, pathname]) => pathname)
// Nothing()
Current and busted
const url = obj?.some?.deep?.property.url ?? ''
const match = url.match(/^https:\/\/(.*)/)
let pathname = null
if (match !== null) {
pathname = match[1]
}
Either Monad
Either<T>
The Either monad encapsulates error handling (among other things).
Either it's a value (i.e. of type "Right"),
or it's an error (i.e. of type "Left").
If it's a value, we should be able to map over it the same as any Functor.
If it's an error, we skip over the mapping function and return the error value we have to the next functor.
Either Monad
Either<T>
const Right = (value) => ({
map: (fn) => Right(fn(value)),
val: () => value,
type: () => Right.type
})
Right.type = () => 'Right'
const attempt = (thunk) => {
try {
return Right(thunk())
} catch (e) {
return Left(e)
}
}
const Left = (value) => ({
map: () => Left(value),
val: () => value,
type: () => Left.type
})
Left.type = () => 'Left'
Either Monad
Either<T>
const json = `{
"data": [1, 2, 3]
}`
const eitherValOrError =
attempt(() => JSON.parse(json))
eitherValOrError
.map(getProp('data'))
// Right([1, 2, 3])
.map(sum)
// Right(6)
const json = `{
"data": [1, 2,
}`
const eitherValOrError =
attempt(() => JSON.parse(json))
eitherValOrError
.map(getProp('data'))
// Left(Error('SyntaxError'))
.map(sum)
// Left(Error('SyntaxError'))
Handled!
Either<T>
const either = (onLeft) => (onRight) => (Either) => {
switch (Either.type()) {
case Right.type(): { return onRight(Either.val()) }
case Left.type(): { return onLeft(Either.val()) }
default: return Either.val()
}
}
Handled!
Either<T>
const eitherSumOrError = attempt(() => JSON.parse(json))
.map(({data}) => data)
.map(sum)
const handleEither = either
(log('Houston, we have a problem: '))
(log('Grand total: '))
handleEither(eitherSumOrError(`
{ "data": [1, 2, }
`))
// Houston, we have a problem:
// Uncaught SyntaxError: Unexpected token } in JSON at position 15
handleEither(eitherSumOrError(`
{ "data": [1, 2, 3] }
`))
// Grand total: 6
List Monad
List<T>
Lists pretty common in programming, but there are a couple ways we can get in some unexpected states when we are working with them.
- We can attempt to access an out of bounds index
- We can mutate the array while iterating over it
List Monad
List<T>
const List = (values) => ({
map: (fn) => List( ... ),
chain: () => ...,
toString: () => `[${values.join(', ')}]`
})
List Monad
List<T>
const List = (values) => ({
map: (fn) => List(values.map((item) => fn(item))),
toString: () => `[${values.join(', ')}]`
})
const List = (values) => ({
map: (fn) => List(values.map((item) => fn(item))),
filter: (fn) => List(values.filter(fn)),
concat: (arr) => List(values.concat(arr)),
chain: ???,
toString: () => `[${values.join(', ')}]`
})
List Monad
List<T>
const List = (values) => ({
map: (fn) => List(values.map((item) => fn(item))),
filter: (fn) => List(values.filter(fn)),
concat: (arr) => List(values.concat(arr)),
toString: () => `[${values.join(', ')}]`
})
const List = (values) => ({
map: (fn) => List(values.map((item) => fn(item))),
filter: (fn) => List(values.filter(fn)),
concat: (arr) => List(values.concat(arr)),
head: () => values[0],
toString: () => `[${values.join(', ')}]`
})
There is a lot of other really cool things we can do with lists when they are conceptualized in a Monad, such as working with infinite lists, asynchronous lists, observables, etc. If you are interested, I courage you to look deeper at what Monads can do for Lists, for You, and for your applications.
List Monad
List<T>
tapadh leibh, a chairdean.
Thank you, friends
A bheil ceistean ann?
Are there any questions?
Tha mi an dòchas gum bi latha math leibh
I hope you have a good day.
cory brown
why yes, we are hiring. thank you for asking
You Probably Already Understand Mondads
By Cory Brown
You Probably Already Understand Mondads
Monads are (in)famously said to be cursed. Once one understands what a Monad is, one loses the ability to explain it to others. This is has become something of a self-fulfilling prophecy. Today, we are going to break that curse.
- 1,134