Skip to content

Instantly share code, notes, and snippets.

@tlareg
Last active March 3, 2024 09:43
Show Gist options
  • Save tlareg/1b56607c8fca5f235db445a9ac619880 to your computer and use it in GitHub Desktop.
Save tlareg/1b56607c8fca5f235db445a9ac619880 to your computer and use it in GitHub Desktop.
functor monad applicative
// http://hackage.haskell.org/package/base-4.9.1.0/docs/src/GHC.Base.html
// (f, g, h) => x => h(f(g(x)))
const compose =
(...fns) =>
initial =>
fns.reduceRight(
(result, fn) => fn(result),
initial
)
// class Functor f where
// fmap :: (a -> b) -> f a -> f b
// can also use function instead of class
//
// const SimpleFunctor = val =>
// ({
// map: f => SimpleFunctor(f(val))
// })
class SimpleFunctor {
constructor(val) {
this.val = val
}
// of :: Functor f => a -> f a
static of(val) {
return new SimpleFunctor(val)
}
// map :: Functor f => f a -> (a -> b) -> f b
static map(functor, fn) {
return SimpleFunctor.of(fn(functor.val))
}
map(fn) {
return SimpleFunctor.map(this, fn)
}
}
// What is monad?
//
// First, understand first-class functions. A->B is a function that accepts an type A and returns a type B.
// Then understand polymorphic types. Type A B is a type A parameterized by type B. For instance, List B is a list of Bs.
// Now a monad is simply any abstraction that satisfies the interface:
//
// bind: M B -> (B -> M C) -> M C
// return: B -> M B
//
// source: https://www.reddit.com/r/programming/comments/6boxg8/from_net_to_scala_and_beyond_a_journey_to/dhovuxm/
// class Monad m where
// (>>=) :: m a -> (a -> m b) -> m b ---> bind
// (>>) :: m a -> m b -> m b
// return :: a -> m a ---> of
// fail :: String -> m a
// can also use function instead of class
//
// const SimpleMonad = val =>
// ({
// map: f => SimpleMonad(f(val)),
// join: () => val,
// bind: f => f(val) // f is like a -> m b
// })
class SimpleMonad {
constructor(val) {
this.val = val
}
// aka unit, return, resolve
// of :: Monad f => a -> f a
static of(val) {
return new SimpleMonad(val)
}
// aka fmap, <$>
// map :: Monad m => m a -> (a -> b) -> m b
static map(monad, fn) {
return SimpleMonad.of(fn(monad.val))
}
map(fn) {
return SimpleMonad.map(this, fn)
}
// aka fold
// join :: Monad m => m a -> a
static join(monad) {
return monad.val
}
join() {
return SimpleMonad.join(this)
}
// aka chain, flatMap, then, >>=
// bind :: Monad m => m a -> (a -> m b) -> m b
static bind(monad, fn) {
// monad ---> SimpleMonad(a)
// fn ---> a => SimpleMonad(b)
// .map(fn) ---> SimpleMonad(SimpleMonad(b))
// .join() ---> SimpleMonad(b)
return monad.map(fn).join()
}
bind(fn) {
return SimpleMonad.bind(this, fn)
}
}
// can also use function instead of class
//
// const SimpleApplicative = val =>
// ({
// map: f => SimpleApplicative(f(val)),
// ap: sa => sa.map(val)
// })
class SimpleApplicative {
constructor(val) {
this.val = val
}
static of(val) {
return new SimpleApplicative(val)
}
map(fn) {
return SimpleApplicative.of(fn(this.val))
}
// aka <*>
// ap :: SimpleApplicative f => f (a -> b) -> f a -> f b
static ap(functor1, functor2) {
return functor2.map(functor1.val)
}
ap(functor) {
return SimpleApplicative.ap(this, functor)
}
}
function testBasics() {
// increment :: Number -> Number
const increment = x => x + 1
// add2 :: Number -> Number
const add2 = compose(increment, increment)
// add4 :: Number -> Number
const add4 = compose(add2, add2)
console.log(
SimpleFunctor.of(1)
.map(increment)
.map(add4)
)
// incrementMonad :: Number -> SimpleMonad
const incrementMonad = compose(SimpleMonad.of, increment)
// add2Monad :: Number -> SimpleMonad
const add2Monad = x => SimpleMonad.of(x).bind(incrementMonad).bind(incrementMonad)
// add4Monad :: Number -> SimpleMonad
// const add4Monad = compose(SimpleMonad.of, add4)
const add4Monad = x => SimpleMonad.of(x).bind(add2Monad).bind(add2Monad)
console.log(
SimpleMonad.of(1)
.bind(incrementMonad)
.bind(add4Monad)
.join()
)
}
function testMonadicLaws() {
const a = { num: 1 };
const k = x => SimpleMonad.of({ num: x.num + 5 });
const h = x => SimpleMonad.of({ num: x.num * x.num })
const m = x => new SimpleMonad(x); // create monad without using 'of' method
const equal = (m1, m2) => m1.val.num === m2.val.num;
/**
* 1) Left Identity
* return a >>= k = k a
*/
(function testFirstLaw() {
const left = SimpleMonad.of(a).bind(k)
const right = k(a)
console.log(`First law: ${ equal(left, right) ? 'ok' : 'fail' }`)
})();
/**
* 2) Right Identity
* m >>= return = m
*/
(function testSecondLaw() {
const left = m(a).bind(SimpleMonad.of);
const right = m(a);
console.log(`Second law: ${ equal(left, right) ? 'ok' : 'fail' }`)
})();
/**
* 3) Associativity
* m >>= (\x -> k x >>= h) = (m >>= k) >>= h
*/
(function testThirdLaw() {
const left = m(a).bind(x => k(x).bind(h));
const right = m(a).bind(k).bind(h);
console.log(`Third law: ${ equal(left, right) ? 'ok' : 'fail' }`)
})();
}
function testApplicative() {
const add = a => b => a + b
const add2 = add(2)
let left = SimpleApplicative.of(add2).ap(SimpleApplicative.of(3))
let right = SimpleApplicative.of(3).map(add2)
console.log(
left.val === right.val // 5
)
left = SimpleApplicative.of(add)
.ap(SimpleApplicative.of(2))
.ap(SimpleApplicative.of(3))
.val
right = add(2, 3)
console.log(
left.val === right.val // 5
)
// const liftA2 = (f, fx, fy) =>
// fx.map(f).ap(fy)
// const liftA3 = (f, fx, fy, fz) =>
// fx.map(f).ap(fy).ap(fz)
// const res = Box(add).ap(Box(2)).ap(Box(4))
// const res = liftA2(add, Box(2), Box(4))
}
// Semigroup - type with a "concat" method that is associative
const Sum = x =>
({
x,
concat: ({x: y}) => Sum(x + y)
})
// Monoid - Semigroup with "empty"
Sum.empty = () => Sum(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment