Bartosz Szewczyk
Front-End developer
Frontend developer @codete
Category Theory for programmers
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."
type Maybe<A> = A | null
function map<A, B>
(m: Maybe<A>, fn: (a: A) => B): Maybe<B> {
if (m === null) {
return null
} else {
return fn(m)
}
}
data Maybe a = Just a | Nothing
map :: Maybe a -> (a -> b) -> Maybe b
map m fn =
case m of
Nothing -> Nothing
Just a -> Just (fn a)
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))
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>
filter(fn: (t: T) => boolean): Maybe<T> {
return this.caseOf({
nothing: () => Maybe.nothing(),
just: (t: T) => fn(t) ? this : Maybe.nothing()
})
}
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'
}
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
})
}
// 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
function twoDivideBy(n: number) : number {
return 2 / n
}
[4, 3, 2, 1, 0].map(twoDivideBy)
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)
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)
}
}
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})
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),
})
Either.right({name, passwd})
.bind(args => validateNameM(args.name))
.bind(args => validatePasswdM(args.passwd))
.do({
left: msg => dispatchErr(msg),
right: args => doRequest(args),
})
EitherPromise.right(fetch(url1))
.caseOf({
resolve: (resp) => right(resp.json())
reject: (err) => left(err)
})
// 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))
// 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))
"The last thing you wanted any programmer to do is mess with internal state(...)" - Alan Kay
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
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]
}
type M<A> = State => [A, State]
type State = number
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]
}
}
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>
}
function tick(): StateM<undefined> {
return new StateM<undefined>((s: State) => {
return [undefined, s + 1]
})
}
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))
)
)
}
Take your State Monad and treat RealWorld as if it were State
type IO a = RealWorld -> (a, RealWorld)
main :: IO ()
main :: RealWorld -> ((), RealWorld)
getLine :: IO String
putStrLn :: String -> IO ()
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
main :: IO ()
main = putStrLn "What is your name?" >>= \() ->
getLine >>= \n ->
putStrLn ("Hello, " ++ n)
const main = () =>
putStrLn("What is your name?").bind(() =>
getLine().bind(n =>
putStrLn("Hello, " + n)
)
Close enough
Implements old Haskell way
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')
})
What if the user was a function?
By Bartosz Szewczyk