如何使用 ContT monad 转换器?
How to use the ContT monad transformer?
ContT
monad 转换器与 Cont
monad 具有相同的实现,但我无法将其应用于所有三种 Either
情况
Right
Left
来自当前的 monadic 动作
Left
来自之前的一元计算
最后一个失败,所有修复它的尝试也都失败了:
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
这令人沮丧。非常感谢任何提示!
您的 main
函数不进行类型检查。
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)))));
首先,让我们简化一下。给定,const pureContT = x => ContT(k => k(x))
我们可以重写 main
如下。
const main = foo =>
contChainT(pureContT(foo)) (mx =>
eithChain(mx) (x =>
pureContT(x === 0
? Left("ouch!")
: Right(x * x))));
然而,chain(pure(x))(f)
与f(x)
相同(左恒等式)。因此,我们可以进一步简化。
const main = mx =>
eithChain(mx) (x =>
pureContT(x === 0
? Left("ouch!")
: Right(x * x)));
到这里,问题就可以看出来了。 eithChain
函数具有以下类型。
Either e a -> (a -> Either e b) -> Either e b
但是,给 eithChain
returns 的回调是 ContT
而不是 Either
。
我实际上会说你处理这个问题是错误的。
ContT r m a = (a -> m r) -> m r
-- Therefore
ContT r (Either e) a = (a -> Either e r) -> Either e r
这不是你想要的。您应该改用 EitherT
转换器。
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
这是我会做的。
// 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!")
作为对 Aadit 回答的补充,我设法实现了一个使用 ContT
作为转换器的版本。我需要操纵当前延续的是标准 Monad Cont
实现和相应的 lift
:
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(k => mmk.contt(x => fmm(x).contt(k)));
const contLiftT = chain => mmk =>
ContT(k => chain(mmk) (k));
const log = x => (console.log(x), x);
const main = foo =>
contChainT(contLiftT(eithChain) (foo)) (x =>
x === 0
? ContT(k => Left("yikes!"))
: ContT(k => k(x * x)));
const foo = main(Right(5)).contt(x => Right(log(x))); // logs 25
const bar = main(Right(0)).contt(x => Right(log(x)));
const baz = main(Left("ouch!")).contt(x => Right(log(x)));
log(foo); // Right(25)
log(bar); // Left("yikes!")
log(baz); // Left("ouch!")
似乎 ContT
/M
带有任意基础 monad M
和 T
/Cont
带有任意转换器 T
在它们的效果中是可交换的。虽然我没有数学技能来证明这种说法。
ContT
monad 转换器与 Cont
monad 具有相同的实现,但我无法将其应用于所有三种 Either
情况
Right
Left
来自当前的 monadic 动作Left
来自之前的一元计算
最后一个失败,所有修复它的尝试也都失败了:
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
这令人沮丧。非常感谢任何提示!
您的 main
函数不进行类型检查。
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)))));
首先,让我们简化一下。给定,const pureContT = x => ContT(k => k(x))
我们可以重写 main
如下。
const main = foo =>
contChainT(pureContT(foo)) (mx =>
eithChain(mx) (x =>
pureContT(x === 0
? Left("ouch!")
: Right(x * x))));
然而,chain(pure(x))(f)
与f(x)
相同(左恒等式)。因此,我们可以进一步简化。
const main = mx =>
eithChain(mx) (x =>
pureContT(x === 0
? Left("ouch!")
: Right(x * x)));
到这里,问题就可以看出来了。 eithChain
函数具有以下类型。
Either e a -> (a -> Either e b) -> Either e b
但是,给 eithChain
returns 的回调是 ContT
而不是 Either
。
我实际上会说你处理这个问题是错误的。
ContT r m a = (a -> m r) -> m r
-- Therefore
ContT r (Either e) a = (a -> Either e r) -> Either e r
这不是你想要的。您应该改用 EitherT
转换器。
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
这是我会做的。
// 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!")
作为对 Aadit 回答的补充,我设法实现了一个使用 ContT
作为转换器的版本。我需要操纵当前延续的是标准 Monad Cont
实现和相应的 lift
:
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(k => mmk.contt(x => fmm(x).contt(k)));
const contLiftT = chain => mmk =>
ContT(k => chain(mmk) (k));
const log = x => (console.log(x), x);
const main = foo =>
contChainT(contLiftT(eithChain) (foo)) (x =>
x === 0
? ContT(k => Left("yikes!"))
: ContT(k => k(x * x)));
const foo = main(Right(5)).contt(x => Right(log(x))); // logs 25
const bar = main(Right(0)).contt(x => Right(log(x)));
const baz = main(Left("ouch!")).contt(x => Right(log(x)));
log(foo); // Right(25)
log(bar); // Left("yikes!")
log(baz); // Left("ouch!")
似乎 ContT
/M
带有任意基础 monad M
和 T
/Cont
带有任意转换器 T
在它们的效果中是可交换的。虽然我没有数学技能来证明这种说法。