Last active
March 3, 2024 09:43
-
-
Save tlareg/1b56607c8fca5f235db445a9ac619880 to your computer and use it in GitHub Desktop.
functor monad applicative
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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