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
export default Layout
λ -> putStrLn ("Hello, FP folks!")
export default Layout
https://github.com/kutyel/fpjs-workshop
export default Layout
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
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
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
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
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
export default Layout
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
export default Layout
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
export default Split
compose(join, map(join)) === compose(join, join)
<img src={require('file-loader!./img/monad_associativity.png')} />
export default SplitRight
compose(join, of) === compose(join, map(of)) === id
<img src={require('file-loader!./img/triangle_identity.png')} />
export default Layout
export default Layout
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