【问题标题】:How to use the ContT monad transformer?如何使用 ContT monad 转换器?
【发布时间】:2021-03-04 22:54:03
【问题描述】:

ContT monad 转换器与 Cont monad 具有相同的实现,但我无法将其应用于所有三个 Either 案例

  • Right
  • Left 来自当前的一元动作
  • 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

这令人沮丧。任何提示都非常感谢!

【问题讨论】:

    标签: javascript functional-programming monads monad-transformers continuations


    【解决方案1】:

    您的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 的回调返回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!")

    【讨论】:

    • 但是我为什么要使用ContT呢?当我查看 Kleisli 箭头 a -> Cont r m b ~ a -> (b -> m r) -> m r ~ (b -> m r) -> (a -> m r) 时,它是域的转换。你有什么可以理解的例子吗?
    • @scriptum ContT monad 最常见的用例是扁平化嵌套的 monadic 计算。以下blog post 的 Roman Cheplyaka 可能会让您感兴趣。
    • @AaditMShah 我实现了一个基于ContT 的版本并将其作为补充答案发布。
    【解决方案2】:

    作为 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 与任意基本单子MT/Cont 与任意转换器T 在其效果上是可交换的。不过,我没有数学能力来证明这种说法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-06
      • 2021-11-27
      • 2011-06-22
      • 1970-01-01
      • 2013-05-01
      • 1970-01-01
      相关资源
      最近更新 更多