首页 > 解决方案 > 如何编写一个 TaskT Monad Transformer 来组合具有其他效果的异步计算?

问题描述

我将 monad 转换器构造为chain/的变体,of它带有一个附加参数 - 外部 monad 的类型目录:

const None =
  ({runOption: null, tag: "None", [Symbol.toStringTag]: "Option"});

const Some = x =>
  ({runOption: x, tag: "Some", [Symbol.toStringTag]: "Option"});
  
const optOfT = of => x => of(Some(x));

const optChainT = ({chain, of}) => fmm => mmx =>
  chain(mx => {
    switch (mx.tag) {
      case "None": return of(None);
      case "Some": return fmm(mx.runOption);
    }
  }) (mmx);

const arrOf = x => [x];

const arrChain = fm => xs =>
  xs.reduce((acc, x) => arrPushFlat(acc) (fm(x)), []);

const arrPushFlat = xs => ys => {
  ys.forEach(x =>
    xs.push(x));

  return xs;
};

const xs = [Some("foo"), None, Some("bar")];

console.log(
  optChainT({chain: arrChain, of: arrOf})
    (s => [Some(s.toUpperCase())]) (xs)); // [Some("FOO"), None, Some("BAR")]

所以基本上一个转换器是两个单子的手写组合,即它需要两个单子并返回一个新的复合单子,因此它本身是可组合的。欢迎使用可组合效果。

但是当懒惰出现在桌面上时,我无法将我的头包裹在 monad 转换器上。如果我想为 monad 创建一个转换器[Task<Option<a>, Error>]怎么办?我需要一个用于异步任务的转换器,即 a tChainT,但是这个运算符会是什么样子?

这是一个机械实现(AFAIK)说明了为什么单子不能以一般方式组合:

const tChainT = ({chain, of}) => fmm => mmx =>
  chain(mx =>
    tChain(fmm) (mx) // A
      ) (mmx); 

A返回 a Task,当运行时最终会产生一个s 的Arrays ,然后将传递给给定的延续。但我需要马上得到结果。TaskOption

这是我的Task实现中与问题相关的部分:

const Task = k =>
  ({runTask: (res, rej) => k(res, rej), [Symbol.toStringTag]: "Task"});

const tChain = fm => mx =>
  Task((res, rej) => mx.runTask(x => fm(x).runTask(res, rej), rej));

const tOf = x => Task((res, rej) => res(x));

标签: javascriptfunctional-programmingmonadsmonad-transformers

解决方案


您问题中的tChainT功能无法按预期工作,因为它的类型错误。

const tChainT = ({chain, of}) => fmm => mmx =>
  chain(mx =>
    tChain(fmm) (mx) // A
      ) (mmx);

// can be simplified to

const tChainT = ({ chain }) => fmm => chain(tChain(fmm));

// tChain  ::                   (a -> Task e b) -> Task e a -> Task e b
//                                                 |______|    |____|
//                                                    |          |
// chain   :: Monad    m     =>                      (a     ->   m    b) -> m        a      ->   m    b
//                    _|__                                                 _|__    __|___       _|__
//                   |    |                                               |    |  |      |     |    |
// tChainT :: Monad (Task e) => (a -> Task e b) ->                        Task e (Task e a) -> Task e b

您答案中的tChainT功能也有错误的类型。

// tChainT :: Monad m => (a -> Task e b) -> m (Task e a) -> m (Task e b)
const tChainT = ({chain, of}) => fm => mmx =>
  chain(mx =>
    of(tChain(fm) (mx))) (mmx); // ***A***

请注意,返回类型fmTask e b而不是m (Task e b)。因此,tChainT不返回有效的一元绑定函数。事实上,创建该类型的函数是不可能的,Monad m => (a -> m (Task e b)) -> m (Task e a) -> m (Task e b)因为monad 转换m (Task e a)器的结构错误。TaskT e正确的结构如下。

// newtype TaskT e m a = TaskT { runTaskT :: (a -> m (), b -> m ()) -> m () }
const TaskT = runTaskT => ({ [Symbol.toStringTag]: "TaskT", runTaskT });

// tChainT :: (a -> TaskT e m b) -> TaskT e m a -> TaskT e m b
const tChainT = f => m => TaskT((res, rej) =>
    m.runTaskT(x => f(x).runTaskT(res, rej), rej));

// tOfT :: a -> TaskT e m a
const tOfT = x => TaskT((res, rej) => res(x));

请注意和分别tChainT与 和tOfT具有相同的实现。这是因为底层的 monad来自解决和拒绝处理程序。因此,延续机制为我们处理它。毕竟只是有一个额外的继续拒绝。tChaintOfTaskTTaskCont


推荐阅读