Skip to content

Latest commit

 

History

History
704 lines (510 loc) · 13.4 KB

deck.mdx

File metadata and controls

704 lines (510 loc) · 13.4 KB

import { Image, Notes, Appear } from 'mdx-deck' import { dark } from 'mdx-deck/themes' import Layout, { Split, SplitRight } from './layout' import codeSurfer from 'prism-react-renderer/themes/nightOwl' export { components } from 'mdx-deck-code-surfer' export const theme = { ...dark, codeSurfer, monospace: '"Dank Mono", "Operator Mono Lig", "Operator Mono", "Fira Code", Consolas, "Roboto Mono", monospace', colors: { text: 'rgb(214, 222, 235)', code: 'rgb(130, 170, 255)', link: 'rgb(128, 203, 196)', background: 'rgb(1, 22, 39)', }, }

<Image src={require('file-loader!./img/lambda-background.png')} />


export default Layout

λ Functional Programming Principles in JavaScript (a Workshop)


export default Layout

λ -> putStrLn ("Hello, FP folks!")


export default Layout

https://github.com/kutyel/fpjs-workshop


export default Layout

Functors

map :: Functor f => (a -> b) -> f a -> f b

const map = curry((f, fx) => fx.map(f))

Identity law: map(id) === id

Composition law: compose(map(f), map(g)) === map(compose(f, g))


export default Layout

Functor is all about function application! 🤯

fmap :: Functor f => (a -> b) -> f a -> f b

<img src={require('file-loader!./img/functormap.png')} />


export default Layout

/**
 * Our first functor! 🎉
 */
const Box = x => ({
  map: f => Box(f(x)),
  inspect: () => `Box(${x})`,
})

Box.of = Box
----
* > Our first functor!
5 > This is the important part, the `map` function! 🗺
6 > This is for Node.js to let us see the inner value 👀
9 > `of` is just an optional constructor function

export default Layout

Box(2).map((two) => two + 2)
// > Box(4)

Box('flamethrowers').map((s) => s.toUpperCase())
// > Box(FLAMETHROWERS)

Box.of('bombs').map(append(' away')).map(prop('length'))
// > Box(10)

export default Layout

Our Box does nothing special... in fact it's called Identity!


export default Layout

Famous Functors: Maybe

data Maybe = Nothing | Just a


export default Layout

Maybe.of('Malkovich Malkovich').map(match(/a/gi))
// Just(True)

Maybe.of(null).map(match(/a/gi))
// Nothing

Maybe.of({ name: 'Boris' }).map(prop('age')).map(add(10))
// Nothing

Maybe.of({ name: 'Dinah', age: 14 }).map(prop('age')).map(add(10))
// Just(24)

export default Layout

// safeHead :: [a] -> Maybe(a)
const safeHead = (xs) => Maybe.of(xs[0])

// streetName :: Object -> Maybe String
const streetName = compose(map(prop('street')), safeHead, prop('addresses'))

streetName({ addresses: [] })
// > Nothing

streetName({ addresses: [{ street: 'Shady Ln.', number: 4201 }] })
// > Just(Shady Ln.)

export default Layout

// withdraw :: Number -> Account -> Maybe(Account)
const withdraw = curry((amount, { balance }) =>
  Maybe.of(balance >= amount ? { balance: balance - amount } : null)
)

// This function is hypothetical, not implemented here... nor anywhere else.
// updateLedger :: Account -> Account
const updateLedger = account => account

// remainingBalance :: Account -> String
const remainingBalance = ({ balance }) => `Your balance is $${balance}`

// finishTransaction :: Account -> String
const finishTransaction = compose(
  remainingBalance,
  updateLedger
)

// getTwenty :: Account -> Maybe(String)
const getTwenty = compose(
  map(finishTransaction),
  withdraw(20)
)

getTwenty({ balance: 200.0 })
// Just('Your balance is $180')

getTwenty({ balance: 10.0 })
// Nothing
----
* > Let's have a look
1:4 > `withdraw` will tip its nose at us and return Nothing if we're short on cash
5:11 > some helper functions
12:17 > if the withdraw fails, then map will sever the rest of our computation
18:23 > this is the important bit
24:29 > it works as expected! 🎉

export default Layout

What if we want to find out why something failed with Maybe? 🤔


export default Layout

Famous Functors: Either

data Either = Left a | Right b


export default Layout

import Either from 'crocks/Either'

const { Left, Right } = Either

Right('rain').map(str => `b${str}`)
// Right('brain')

Left('rain').map(str => `It's gonna ${str}, better bring your umbrella!`)
// Left('rain')

Either.of({ host: 'localhost', port: 80 }).map(prop('host'))
// Right(localhost)

Left('rolls eyes...').map(prop('host'))
// Left('rolls eyes...')
----
* > some contrived examples with Either...
8,9,14,15 > notice mapping on `Left` doesn't run the computations!
5,6,11,12 > whereas mapping over `Right` works exactly as with `Just`.

export default Layout

import Either from 'crocks/Either'
import moment from 'moment'

const { Left, Right } = Either

// getAge :: Date -> User -> Either(String, Number)
const getAge = curry((now, user) => {
  const birthDate = moment(user.birthDate, 'YYYY-MM-DD')

  return birthDate.isValid()
    ? Either.of(now.diff(birthDate, 'years'))
    : Left('Birth date could not be parsed')
})

getAge(moment(), { birthDate: '2005-12-12' })
// Right(9)

getAge(moment(), { birthDate: 'July 4, 2001' })
// Left('Birth date could not be parsed')

export default Layout

Let's talk now about...

Side Effects! 🙀


export default Layout

// ioWindow :: IO Window
const ioWindow = IO.of(() => window)

ioWindow.map((win) => win.innerWidth)
// IO(1430)

ioWindow.map(prop('location')).map(prop('href')).map(split('/'))
// IO(['http:', '', 'localhost:8000', 'blog', 'posts'])

// $ :: String -> IO [DOM]
const $ = (selector) => IO.of(() => document.querySelectorAll(selector))

$('#myDiv')
  .map(head)
  .map((div) => div.innerHTML)
// IO('I am some inner html')

export default Layout

Exercise time! 🏋🏼‍♂️


export default Layout

Applicative Functors

ap :: Applicative f => f (a -> b) -> f a -> f b

Identity law: A.of(id).ap(v) === v

Composition law: A.of(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w))

Homomorphism law: A.of(f).ap(A.of(x)) === A.of(f(x))

Interchange law: v.ap(A.of(x)) === A.of(f => f(x)).ap(v)


export default Layout

// We can't do this because the numbers are bottled up.
add(Box.of(2), Box.of(3))
// NaN

// Let's use our trusty map
const BoxOfAdd2 = map(add, Box.of(2))
// Box(add(2))

Box.of(2).chain(two => Box.of(3).map(add(two)))
----
* > we want to apply one functor to another
1:3 > we can't do this...
5:7 > map is not enough 😭
9 > we can use map AND chain! (chain = flatMap) 🙉

export default Layout

/**
 * Applicative Functors:
 * - F(x).map(f) === F(f).ap(F(x))
 */
const Box = x => ({
  ap: b => b.map(x),
  map: f => Box(f(x)),
  inspect: () => `Box(${x})`,
})

// `ap` is a function that can apply the function contents
// of one functor to the value contents of another!
----
* > Our first applicative functor!
6,11,12 > important! (`b` refers to another Box... or Functor!)

export default Layout

Box.of(add(2)).ap(Box.of(3))
// Box(5)

// all together now

Box.of(2).map(add).ap(Box.of(3))
// Box(5)
----
* > now we can sum values inside Boxes!
1, 6 > Box(3) has been set free from the jail of the nested monadic function!

export default Layout

// Remember homomorphism?
// F.of(x).map(f) === F.of(f).ap(F.of(x))

Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3))
// Maybe(5)

Async.of(add).ap(Async.of(2)).ap(Async.of(3))
// Async(5)
----
* > what we learn applies to all valid functors/applicatives!
4,7 > examples with Maybe and Async

export default Layout

// $ :: String -> IO DOM
const $ = selector => IO.of(() => document.querySelector(selector))

// getVal :: String -> IO String
const getVal = compose(map(prop('value')), $)

// signIn :: String -> String -> Bool -> User
const signIn = curry((username, password, rememberMe) => { /* signing in */ })

IO.of(signIn).ap(getVal('#email')).ap(getVal('#password')).ap(IO.of(false))
// IO({ id: 3, email: 'gg@allin.com' })
----
* > with ap, we can chain many side effects together!

export default Layout

const liftA2 = curry((g, f1, f2) => f1.map(g).ap(f2))

const liftA3 = curry((g, f1, f2, f3) => f1.map(g).ap(f2).ap(f3))

// liftA4, etc

// ...

liftA2(add, Maybe(2), Maybe(5)) // > Maybe(7)

liftA3(signIn, getVal('#email'), getVal('#password'), IO.of(false))
----
* > Pointfree Style™️ strikes back!
1:5 > yeah, we lift!
9 > add values (arity 2)
11 > previous example refactored with liftA3 🧨 (arity 3)

export default Layout

// Haskell
add <$> Right 2 <*> Right 3

// JavaScript
map(add, Right(2)).ap(Right(3))

export default Layout

Moar exercises! 🤸🏼‍♀️


export default Layout

Monads

flatMap :: Monad m => (a -> m b) -> m a -> m b

const flatMap = curry((f, m) => m.map(f).join())

const mcompose = (f, g) => compose(chain(f), g)

Left identity law: mcompose(M, f) === f

Right identity law: mcompose(f, M) === f

Associativity law: mcompose(mcompose(f, g), h) === mcompose(f, mcompose(g, h))


export default Layout

// safeProp :: Key -> {Key: a} -> Maybe a
const safeProp = curry((x, obj) => Maybe.of(obj[x]))

// safeHead :: [a] -> Maybe a
const safeHead = safeProp(0)

// firstAddressStreet :: User -> Maybe (Maybe (Maybe Street))
const firstAddressStreet = compose(
  map(map(safeProp('street'))),
  map(safeHead),
  safeProp('addresses'),
)

firstAddressStreet({
  addresses: [{ street: { name: 'Mulburry', number: 8402 }, postcode: 'WC2N' }],
})
// Maybe(Maybe(Maybe({name: 'Mulburry', number: 8402})))
----
* > we want to access something deeply nested...
1:5 > this two are `getProp` in crocks...
7:12 > Ouff.... that's a lot of fmapping!!
17 > And we end up with a beautiful chain!!

export default Layout

How can we break the chain? ⛓

Monads are pointed functors that can flatten

What is special about Monad?

join :: Monad m => m (m a) -> m a

const join = mma => mma.join()


export default Layout

/**
 * Box, Either, Task (Async), List...
 * F.of / pure (pointed functor) + chain (flatMap, bind, >>=)
 *
 * - Associativity: join(m.map(join)) === join(join(m))
 * - Left/Right identity: join(Box.of(m)) === join(m.map(Box.of))
 */
const Box = x => ({
  ap: b => b.map(x),
  chain: f => f(x),
  map: f => Box(f(x)),
  inspect: () => `Box(${x})`
})

Box.of = Box

//    join :: Monad m => m (m a) -> m a
const join = m => m.chain(x => x)
----
* > Our first monad!
15 > this is what we mean by "pointed functor"
10 > here's our *magic* chain function! ⛓
17,18 > we can also define join from outside calling chain!
2 > Task (Folktale) = Async (Crocks)

export default Layout

// join :: Monad m => m (m a) -> m a
const join = mma => mma.join()

// firstAddressStreet :: User -> Maybe Street
const firstAddressStreet = compose(
  join,
  map(safeProp('street')),
  join,
  map(safeHead),
  safeProp('addresses')
)

firstAddressStreet({
  addresses: [{ street: { name: 'Mulburry', number: 8402 }, postcode: 'WC2N' }],
})
// Maybe({name: 'Mulburry', number: 8402})
----
* > we achieved what we wanted!
16 > yay! 🎉
6:9 > mmmmmhh... that pattern looks suspicious! 🤔

export default Layout

// chain :: Monad m => (a -> m b) -> m a -> m b
const chain = curry((f, m) => m.map(f).join())

// or

// chain :: Monad m => (a -> m b) -> m a -> m b
const chain = f => compose(join, map(f))
----
* > chain is just (join . map) 🤯

export default Layout

// map/join
const firstAddressStreet = compose(
  join,
  map(safeProp('street')),
  join,
  map(safeHead),
  safeProp('addresses'),
)

// chain
const firstAddressStreet = compose(
  chain(safeProp('street')),
  chain(safeHead),
  safeProp('addresses'),
)
----
1:8 > before...
10:15 > ...after! 🙌🏼

export default Layout

A Glimpse of Math ♾


export default Split

Associativity Law

compose(join, map(join)) === compose(join, join)

<img src={require('file-loader!./img/monad_associativity.png')} />


export default SplitRight

Double Identity Law

compose(join, of) === compose(join, map(of)) === id

<img src={require('file-loader!./img/triangle_identity.png')} />


export default Layout

Last exercises! 🚀🚀🚀


export default Layout

Resources:

https://crocks.dev/

https://ramdajs.com/docs/

https://mostly-adequate.gitbooks.io/mostly-adequate-guide/

https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript


export default Layout

Thanks! 😸