Monads in Javascript

Bartosz Szewczyk

Frontend developer @codete

Contents:

  1. About Monads
  2. Maybe Monad
  3. Either Monad
  4. State Monad
  5. IO Monad

About monads

“Monad is a monoid in the category of endofunctors”

Bartosz Milewski

Category Theory for programmers

Fantasy-land

Specification for interoperability of common algebraic structures in JavaScript

https://github.com/fantasyland/fantasy-land

"A value that implements the Monad specification must also implement the Applicative and Chain specifications."

source: http://leftoversalad.com

Mattias Petter Johansson:

If you know map, I will teach you monads

Douglas Crockford:

Monads and Gonads

Igal Tabachnik

Dan Meyer

Maybe monad

No null type in Haskell

"I call it my billion-dollar mistake" - Tony Hoare about null reference

type Maybe<A> = A | null

Typescript

function map<A, B>
(m: Maybe<A>, fn: (a: A) => B): Maybe<B> {
  if (m === null) {
    return null
  } else {
    return fn(m)
  }
}

Typescript

data Maybe a = Just a | Nothing

Haskell

map :: Maybe a -> (a -> b) -> Maybe b
map m fn =
  case m of
    Nothing -> Nothing
    Just a -> Just (fn a)

Haskell

Composition problem

map(
  map(
    bind(
      unit(a),
      x => foo(x)
    ), y => bar(y)
  ), z => baz(z)
)
Maybe.unit(a)
  .bind(x => foo(x))
  .map(y => bar(y))
  .map(z => baz(z))

Monad  class

class Maybe<T> {

  static unit<T>(t: T | null): Maybe<T>

  map<U>(fn: (t: T) => U) : Maybe<U>

  bind<U>(fn: (t: T) => Maybe<U>): Maybe<U>

  caseOf<U>(cases: MaybeCases<T, U>): Maybe<U>

}

function join<T>(m: Maybe<Maybe<T>>): Maybe<T>

Monad  methods

filter(fn: (t: T) => boolean): Maybe<T> {
  return this.caseOf({
    nothing: () => Maybe.nothing(),
    just: (t: T) => fn(t) ? this : Maybe.nothing()
  })
}

Imperative way

function getCountry(student) {
  const school = student.school()
  if (scholl !== null) {
    const addr = school.address()
    if (addr !== null) {
      return addr.country()
    }
  }
  return 'Country does not exist'
}

Monadic way

function getCountry(student) {
  return maybe(student)
    .map(student => student.school())
    .map(school => school.address())
    .map(addr => addr.country())
    .caseOf({
      nothing: () => 'Country does not exists',
      just: country => country
    })
}

Monadic laws

// Left identity:
unit(a).bind(f) === f(a)

// Right identity:
m.bind(unit) === m

// Associativity:
m.bind(f).bind(g) === m.bind(x => f(x).bind(g))
Could also be unit, join, map

Either Monad

No exceptions in Haskell

"There is no smart quote that disqualifies usage of exceptions" - Bartosz Szewczyk

function twoDivideBy(n: number) : number {
  return 2 / n
}

[4, 3, 2, 1, 0].map(twoDivideBy)

Typescript

function twoDivideBy(n: number) : number {
  if (n === 0)
    throw new Error('Cannot divide by zero')
  return 2 / n
}

[4, 3, 2, 1, 0].map(twoDivideBy)

Exceptions?

type Either<L, R> = Left<L> | Right<R>

function twoDivideBy(n: number)
  : Either<string, number>
{
  if (n === 0) {
    return Either.left(
      "Cannot divide by zero")
  } else {
    return Either.right(2 / n)
  }
}

Either good or bad

const validName = validateName(name)
if (!validName) {
  return dispatchErr('Not valid name')
}

const validPasswd = validatePasswd(password)
if (!validPasswd) {
  return dispatchErr('Not valid password')
}

doRequest({name, password})

Typescript

Either.right({name, passwd})
  .bind(args => validateName(args.name)
    ? Either.right(args)
    : Either.left('Not valid name')
  )
  .bind(args => validatePasswd(args.passwd)
    ? Either.right(args)
    : Either.left('Not a valid password')
  )
  .do({
    left: msg => dispatchErr(msg),
    right: args => doRequest(args),
  })

Typescript

Either.right({name, passwd})
  .bind(args => validateNameM(args.name))
  .bind(args => validatePasswdM(args.passwd))
  .do({
    left: msg => dispatchErr(msg),
    right: args => doRequest(args),
  })

Typescript

EitherPromise.right(fetch(url1))
  .caseOf({
    resolve: (resp) => right(resp.json())
    reject: (err) => left(err)
  })

Promises

// Left identity (f must return Promise):
Promise.resolve(a).then(f) === f(a)

// Right identity:
p.then(Promise.resolve) === p

// Associativity (f must return Promise):
p.then(f).then(g) === p.then(x => f(x).then(g))

Promises & Monadic laws

// Left identity:
Observable.of(a).switchMap(f) === f(a)

// Right identity:
stream.switchMap(Observable.of) === stream

// Associativity:
stream.switchMap(f).switchMap(g) ===
  stream.switchMap(x => f(x).switchMap(g))

Streams & Monadic laws

State Monad

No mutations in Haskell

"The last thing you wanted any programmer to do is mess with internal state(...)" - Alan Kay

Recursion problem

type Con = {con: number}
type Div = {div: [Tree, Tree]}
type Tree = Div | Con

eval({
  div: [
    {
      div: [
        {con: 1972},
        {con: 2}
       ]
    },
    {con: 23}
  ]
})
(1972 / 2) / 23

Counting recursions

function eval(tree: Tree, n: number) {
  if (isCon(tree)) {
    return [tree.con, n]
  }
  const [t1, n1] = eval(tree.div[0], n)
  const [t2, n2] = eval(tree.div[1], n1)
  return [t1 / t2, n2 + 1]
}

Enter the State Monad

type M<A> = State => [A, State]
type State = number

Enter the State Monad

function eval(tree: Tree): M<number> {
  if (isCon(tree)) {
    return (s: State) => [tree.con, s]
  }
  return (s: State) => {
    const [t1, s1] = eval(tree.div[0])(s)
    const [t2, s2] = eval(tree.div[1])(s1)
    return [t1 / t2, s2 + 1]
  }
}

State Monad functions

class StateM<T> {

  constructor(f: (s: State) => [T, State])

  static unit<T>(t: T): StateM<T>

  bind<U>(f: (t0: T) => StateM<U>): StateM<U>
}

Small addition for the sake example

function tick(): StateM<undefined> {
  return new StateM<undefined>((s: State) => {
    return [undefined, s + 1]
  })
}

State Monad revisited

function eval(tree: Tree): StateM<number> {
  if (isCon(tree)) {
    return StateM.unit(tree.con)
  }
  return eval(tree.div[0]).bind(t1 => 
    eval(tree.div[1]).bind(t2 =>
      tick().bind(() => StateM.unit(t1 / t2))
    )
  )
}

IO Monad

IO Monad

Take your State Monad and treat RealWorld as if it were State

IO as basic block

type IO a = RealWorld -> (a, RealWorld)

main :: IO ()
main :: RealWorld -> ((), RealWorld)

getLine :: IO String
putStrLn :: String -> IO ()

Won't work in Haskell

main :: IO ()
main = putStrLn "What is your name?" >>= \() ->
         putStrLn ("Hello, " ++ getLine)

Couldn’t match expected type String with actual type IO String
In the second argument of (++), namely getLine

Will work

main :: IO ()
main = putStrLn "What is your name?" >>= \() ->
         getLine >>= \n ->
           putStrLn ("Hello, " ++ n)

"JS" kinda way

const main = () =>
  putStrLn("What is your name?").bind(() => 
    getLine().bind(n =>
      putStrLn("Hello, " + n)
    )

Cycle.js

Close enough

Implements old Haskell way

Basic cycle example

import { run } from '@cycle/run'

run(function main(sources) {
  return {
    DOM: sources.DOM.select('.counter')
      .events('click').map(e => e)
      .fold((agg, i) => agg + 1, 0)
      .map(clicks => (
        <div>
          <div>Clicks: ${clicks}</div>,
          <button className="counter">Click me!</button>
        </div>
      ))
  }
}, {
  DOM: makeDOMDriver('#app')
})

Andre Staltz:

What if the user was a function?

 

That's
all
Folks

Exhausting list of materials:

Thank you for listening

twitter: @sztobar

github: sztobar

Monads in Javascript

By Bartosz Szewczyk

Monads in Javascript

  • 1,987