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