TaskT를 Trampoline의 모나드 인스턴스와 결합하여 스택리스 비동기 계산을 얻는 방법은 무엇입니까?

Dec 13 2020

Trampoline모나드이며 모나드 변환기 스택에 스택 안전성을 추가합니다. monadRec모나드 계산의 결과를 제공 하는 특수 인터프리터 ( ) 에 의존하여이를 달성합니다 (실제로는 무료 모나드 패턴의 특수 버전입니다). 이러한 이유로 Trampoline모나드는 가장 바깥 쪽 모나드, 즉 트랜스포머 스택의 기본 모나드 여야합니다.

다음 설정 TaskT(기본적 Cont으로 공유와 함께 사용)에서 모나드 변환기와 Trampoline기본 모나드가 있습니다.

// TASK

const TaskT = taskt => record(
  TaskT,
  thisify(o => {
    o.taskt = k =>
      taskt(x => {
        o.taskt = k_ => k_(x);
        return k(x);
      });

    return o;
  }));

// Monad

const taskChainT = mmx => fmm =>
  TaskT(k =>
    mmx.taskt(x =>
      fmm(x).taskt(k)));

const taskOfT = x =>
  TaskT(k => k(x));

// Transformer

const taskLiftT = chain => mmx =>
  TaskT(k => chain(mmx) (k));

// auxiliary functions

const taskAndT = mmx => mmy =>
  taskChainT(mmx) (x =>
    taskChainT(mmy) (y =>
      taskOfT([x, y])));

const delayTaskT = f => ms => x =>
  TaskT(k => setTimeout(comp(k) (f), ms, x));

const record = (type, o) => (
  o[Symbol.toStringTag] = type.name || type, o);

const thisify = f => f({});

const log = (...ss) =>
  (console.log(...ss), ss[ss.length - 1]);

// TRAMPOLINE

const monadRec = o => {
  while (o.tag === "Chain")
    o = o.fm(o.chain);

  return o.tag === "Of"
    ? o.of
    : _throw(new TypeError("unknown trampoline tag"));
};

// tags

const Chain = chain => fm =>
  ({tag: "Chain", fm, chain});


const Of = of =>
  ({tag: "Of", of});

// Monad

const recOf = Of;

const recChain = mx => fm =>
  mx.tag === "Chain" ? Chain(mx.chain) (x => recChain(mx.fm(x)) (fm))
    : mx.tag === "Of" ? fm(mx.of)
    : _throw(new TypeError("unknown trampoline tag"));

// MAIN

const foo = x =>
  Chain(delayTaskT(x => x) (0) (x)) (Of);

const bar = taskAndT(
  taskLiftT(recChain) (foo(1)))
    (taskLiftT(recChain) (foo(2))); // yields TaskT

const main = bar.taskt(x => Of(log(x))); // yields Chain({fm, chain: TaskT})

monadRec(main); // yields [TaskT, TaskT] but [1, 2] desired

Trampoline이벤트 루프 전에 강제 평가가 비동기 작업의 결과를 받기 때문에 이것은 내가 원하는 것이 아닙니다 . 내가 필요한 것은 다른 방법이지만 이미 언급했듯이 TrampolineT변압기 가 없습니다 . 내가 무엇을 놓치고 있습니까?

답변

2 AaditMShah Dec 14 2020 at 12:10

이 코드 조각에는 몇 가지 문제가 있습니다.

이슈 # 1 :에 대한 모나드 변압기가 없습니다 IO(예 Task)

.NET 용 모나드 변환기가 없다는 것은 잘 알려져 IO있습니다. [1] 귀하의 TaskT유형을 모델로 ContT하고, ContT실제로 모나드 변압기이다. 그러나을 사용 하여 문제가 발생 TaskT하는와 같은 비동기 계산을 수행 setTimeout하고 있습니다.

TaskT와 유사한 의 정의를 고려하십시오 ContT.

newtype TaskT r m a = TaskT { taskt :: (a -> m r) -> m r }

따라서 delayTaskT유형은 (a -> b) -> Number -> a -> TaskT r m b.

const delayTaskT = f => ms => x =>
  TaskT(k => setTimeout(comp(k) (f), ms, x));

그러나 setTimeout(comp(k) (f), ms, x)유형과 일치하지 않는 시간 초과 ID를 반환합니다 m r. 주 k => setTimeout(comp(k) (f), ms, x)유형을 가져야한다 (b -> m r) -> m r.

사실, m r연속 k이 비동기 적 으로 호출 될 때 유형의 값을 활용하는 것은 불가능합니다 . ContT모나드 변압기는 동기 계산에 사용할 수 있습니다.

그럼에도 불구하고, 우리는 정의 할 수 Task의 전문 버전으로 Cont.

newtype Task a = Task { task :: (a -> ()) -> () } -- Task = Cont ()

따라서 Task모나드 트랜스포머 스택에가있을 때마다 항상베이스에있을 것 IO입니다.

Task모나드 스택을 안전하게 만들고 싶다면 다음 답변 을 읽으십시오 .

문제 # 2 : foo함수에 잘못된 반환 유형이 있습니다.

잠시 delayTaskT동안 올바른 유형이 있다고 가정 해 봅시다 . 이미 알고 있듯이 다음 문제 foo는 잘못된 반환 유형 이 있다는 것입니다.

문제는 fooa로 TaskT래핑 된 것을 반환하는 것으로 보이며이 Chain래핑 TaskTTaskT체인 에서 완전히 분리 되어 절대로 평가 / 발사되지 않습니다.

나는 예상되는 유형 fooa -> TaskT r Trampoline a. 그러나의 실제 유형은 foo입니다 a -> Trampoline (TaskT r m a). 다행히도 수정은 쉽습니다.

const foo = delayTaskT(x => x) (0);

의 유형 foo과 동일합니다 taskOfTa -> TaskT r m a. 우리는 전문화 할 수 있습니다 m = Trampoline.

문제 # 3 : taskLiftT올바르게 사용하고 있지 않습니다.

taskLiftT함수는 기본 모나드 계산을 TaskT계층 으로 들어 올립니다 .

taskLiftT :: (forall a b. m a -> (a -> m b) -> m b) -> m a -> TaskT r m a

taskLiftT(recChain) :: Trampoline a -> TaskT r Trampoline a

지금, 당신은 적용하고 taskLiftT(recChain)foo(1)foo(2).

foo :: a -> Trampoline (TaskT r m a) -- incorrect definition of foo

foo(1) :: Trampoline (TaskT r m Number)
foo(2) :: Trampoline (TaskT r m Number)

taskLiftT(recChain) (foo(1)) :: TaskT r Trampoline (TaskT r m Number)
taskLiftT(recChain) (foo(2)) :: TaskT r Trampoline (TaskT r m Number)

그러나 올바른 정의를 사용 foo하면 유형이 일치하지 않을 것입니다.

foo :: a -> TaskT r Trampoline a -- correct definition of foo

foo(1) :: TaskT r Trampoline Number
foo(2) :: TaskT r Trampoline Number

-- Can't apply taskLiftT(recChain) to foo(1) or foo(2)

의 올바른 정의를 사용하는 경우을 정의 foo하는 두 가지 방법이 bar있습니다. 을 foo사용하여 올바르게 정의 할 수있는 방법은 없습니다 setTimeout. 따라서, 나는 재정의 한 foo것처럼 taskOfT.

  1. 사용 foo하고 사용 하지 마십시오 taskLiftT.

    const bar = taskAndT(foo(1))(foo(2)); // yields TaskT
    

    // TASK
    
    const TaskT = taskt => record(
      TaskT,
      thisify(o => {
        o.taskt = k =>
          taskt(x => {
            o.taskt = k_ => k_(x);
            return k(x);
          });
    
        return o;
      }));
    
    // Monad
    
    const taskChainT = mmx => fmm =>
      TaskT(k =>
        mmx.taskt(x =>
          fmm(x).taskt(k)));
    
    const taskOfT = x =>
      TaskT(k => k(x));
    
    // Transformer
    
    const taskLiftT = chain => mmx =>
      TaskT(k => chain(mmx) (k));
    
    // auxiliary functions
    
    const taskAndT = mmx => mmy =>
      taskChainT(mmx) (x =>
        taskChainT(mmy) (y =>
          taskOfT([x, y])));
    
    const delayTaskT = f => ms => x =>
      TaskT(k => setTimeout(comp(k) (f), ms, x));
    
    const record = (type, o) => (
      o[Symbol.toStringTag] = type.name || type, o);
    
    const thisify = f => f({});
    
    const log = (...ss) =>
      (console.log(...ss), ss[ss.length - 1]);
    
    // TRAMPOLINE
    
    const monadRec = o => {
      while (o.tag === "Chain")
        o = o.fm(o.chain);
    
      return o.tag === "Of"
        ? o.of
        : _throw(new TypeError("unknown trampoline tag"));
    };
    
    // tags
    
    const Chain = chain => fm =>
      ({tag: "Chain", fm, chain});
    
    
    const Of = of =>
      ({tag: "Of", of});
    
    // Monad
    
    const recOf = Of;
    
    const recChain = mx => fm =>
      mx.tag === "Chain" ? Chain(mx.chain) (x => recChain(mx.fm(x)) (fm))
        : mx.tag === "Of" ? fm(mx.of)
        : _throw(new TypeError("unknown trampoline tag"));
    
    // MAIN
    
    const foo = taskOfT;
    
    const bar = taskAndT(foo(1))(foo(2)); // yields TaskT
    
    const main = bar.taskt(x => Of(log(x))); // yields Chain({fm, chain: TaskT})
    
    monadRec(main); // yields [TaskT, TaskT] but [1, 2] desired

  2. 사용하지 마십시오 foo사용 taskLiftT.

    const bar = taskAndT(
      taskLiftT(recChain) (Of(1)))
        (taskLiftT(recChain) (Of(2))); // yields TaskT
    

    // TASK
    
    const TaskT = taskt => record(
      TaskT,
      thisify(o => {
        o.taskt = k =>
          taskt(x => {
            o.taskt = k_ => k_(x);
            return k(x);
          });
    
        return o;
      }));
    
    // Monad
    
    const taskChainT = mmx => fmm =>
      TaskT(k =>
        mmx.taskt(x =>
          fmm(x).taskt(k)));
    
    const taskOfT = x =>
      TaskT(k => k(x));
    
    // Transformer
    
    const taskLiftT = chain => mmx =>
      TaskT(k => chain(mmx) (k));
    
    // auxiliary functions
    
    const taskAndT = mmx => mmy =>
      taskChainT(mmx) (x =>
        taskChainT(mmy) (y =>
          taskOfT([x, y])));
    
    const delayTaskT = f => ms => x =>
      TaskT(k => setTimeout(comp(k) (f), ms, x));
    
    const record = (type, o) => (
      o[Symbol.toStringTag] = type.name || type, o);
    
    const thisify = f => f({});
    
    const log = (...ss) =>
      (console.log(...ss), ss[ss.length - 1]);
    
    // TRAMPOLINE
    
    const monadRec = o => {
      while (o.tag === "Chain")
        o = o.fm(o.chain);
    
      return o.tag === "Of"
        ? o.of
        : _throw(new TypeError("unknown trampoline tag"));
    };
    
    // tags
    
    const Chain = chain => fm =>
      ({tag: "Chain", fm, chain});
    
    
    const Of = of =>
      ({tag: "Of", of});
    
    // Monad
    
    const recOf = Of;
    
    const recChain = mx => fm =>
      mx.tag === "Chain" ? Chain(mx.chain) (x => recChain(mx.fm(x)) (fm))
        : mx.tag === "Of" ? fm(mx.of)
        : _throw(new TypeError("unknown trampoline tag"));
    
    // MAIN
    
    const foo = taskOfT;
    
    const bar = taskAndT(
      taskLiftT(recChain) (Of(1)))
        (taskLiftT(recChain) (Of(2))); // yields TaskT
    
    const main = bar.taskt(x => Of(log(x))); // yields Chain({fm, chain: TaskT})
    
    monadRec(main); // yields [TaskT, TaskT] but [1, 2] desired


[1] Haskell에 IO 변환기가없는 이유는 무엇입니까?