The ContT
monad transformer has the same implementation like the Cont
monad, but I'm not able to apply it to all three Either
cases
Right
Left
from the current monadic actionLeft
from a previous monadic computationThe last one fails and all attempts to fix it failed as well:
const record = (type, o) => (
o[Symbol.toStringTag] = type.name || type,
o);
const union = type => (tag, o) => (
o[Symbol.toStringTag] = type,
o.tag = tag.name || tag,
o);
const match = (tx, o) => o[tx.tag] (tx);
const Either = union("Either");
const Left = left => Either(Left, {left});
const Right = right => Either(Right, {right});
const eithChain = mx => fm =>
match(mx, {
Left: _ => mx,
Right: ({right: x}) => fm(x)
});
const ContT = contt => record(ContT, {contt});
const contChainT = mmk => fmm =>
ContT(c => mmk.contt(x => fmm(x).contt(c)));
const main = foo =>
contChainT(ContT(k => k(foo))) (mx =>
eithChain(mx) (x =>
x === 0
? ContT(k => k(Left("ouch!")))
: ContT(k => k(Right(x * x)))));
main(Right(5)).contt(console.log); // Right(25)
main(Right(0)).contt(console.log); // Left("ouch!")
main(Left("yikes!")).contt(console.log); // Type Error
This is fruststrating. Any hints are much appreciated!
Your main
function does not type check.
const main = foo =>
contChainT(ContT(k => k(foo))) (mx =>
eithChain(mx) (x =>
x === 0
? ContT(k => k(Left("ouch!")))
: ContT(k => k(Right(x * x)))));
First, let's simplify it. Given, const pureContT = x => ContT(k => k(x))
we can rewrite main
as follows.
const main = foo =>
contChainT(pureContT(foo)) (mx =>
eithChain(mx) (x =>
pureContT(x === 0
? Left("ouch!")
: Right(x * x))));
However, chain(pure(x))(f)
is the same as f(x)
(left identity law). Hence, we can further simplify.
const main = mx =>
eithChain(mx) (x =>
pureContT(x === 0
? Left("ouch!")
: Right(x * x)));
Here, you can see the problem. The eithChain
function has the following type.
Either e a -> (a -> Either e b) -> Either e b
However, the callback given to eithChain
returns a ContT
instead of an Either
.
I would actually say that you're approaching this problem wrong.
ContT r m a = (a -> m r) -> m r
-- Therefore
ContT r (Either e) a = (a -> Either e r) -> Either e r
This is not what you want. You should be using the EitherT
transformer instead.
EitherT e m a = m (Either e a)
Cont r a = (a -> r) -> r
-- Therefore
EitherT e (Cont r) a = Cont r (Either e a) = (Either e a -> r) -> r
Here's what I would do.
// Left : e -> Either e a
const Left = error => ({ constructor: Left, error });
// Right : a -> Either e a
const Right = value => ({ constructor: Right, value });
// Cont : ((a -> r) -> r) -> Cont r a
const Cont = runCont => ({ constructor: Cont, runCont });
// either : (e -> b) -> (a -> b) -> Either e a -> b
const either = left => right => either => {
switch (either.constructor) {
case Left: return left(either.error);
case Right: return right(either.value);
}
};
// MonadEitherT : Monad m -> Monad (EitherT e m)
const MonadEitherT = ({ pure, bind }) => ({
pure: x => pure(Right(x)),
bind: m => f => bind(m)(either(e => pure(Left(e)))(f))
});
// MonadCont : Monad (Cont r)
const MonadCont = {
pure: x => Cont(k => k(x)),
bind: m => f => Cont(k => m.runCont(x => f(x).runCont(k)))
};
// MonadEitherCont : Monad (EitherT e (Cont r))
const MonadEitherCont = MonadEitherT(MonadCont);
// main : Either String Number -> EitherT String (Cont r) Number
const main = either => MonadEitherCont.bind(MonadCont.pure(either))(x =>
MonadCont.pure(x === 0 ? Left("ouch!") : Right(x * x)));
// show : Either e a -> String
const show = either
(e => `Left(${JSON.stringify(e)})`)
(x => `Right(${JSON.stringify(x)})`);
// print : Either e a -> ()
const print = x => console.log(show(x));
main(Right(5)).runCont(print); // Right(25)
main(Right(0)).runCont(print); // Left("ouch!")
main(Left("yikes!")).runCont(print); // Left("yikes!")