Jak połączyć TaskT z monadą Trampoline, aby uzyskać niezliczone obliczenia asynchroniczne?

Dec 13 2020

Trampolinejest monadą i dodaje bezpieczeństwo stosu do stosu transformatora monady. Osiąga to opierając się na specjalnym interpreter ( monadRec), który jest zasilany wynikiem obliczeń monadycznych (w rzeczywistości jest to wyspecjalizowana wersja wzoru wolnej monady). Z tego powodu Trampolinemonada musi być monadą najbardziej zewnętrzną, czyli monadą podstawową stosu transformatorów.

W następującym ustawieniu TaskT(które zasadniczo wiąże się Contze współdzieleniem) znajduje się transformator monady i Trampolinemonada podstawowa:

// 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

Nie o to mi chodzi, ponieważ Trampolineocena wymuszeń, zanim pętla zdarzeń otrzyma wynik zadań asynchronicznych. Potrzebuję na odwrót, ale jak już wspomniałem, nie ma TrampolineTtransformatora. czego mi brakuje?

Odpowiedzi

2 AaditMShah Dec 14 2020 at 12:10

W tym fragmencie kodu występuje kilka problemów.

Problem nr 1: Nie ma transformatora monadowego dla IO(tj. Task)

Powszechnie wiadomo, że nie ma transformatora monadowego IO. [1] Twój TaskTtyp jest wzorowany na ContTi ContTrzeczywiście jest transformatorem monadowym. Jednak używasz TaskTdo wykonywania obliczeń asynchronicznych, takich jak setTimeout, gdzie pojawia się problem.

Rozważ definicję TaskT, która jest podobna do ContT.

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

Dlatego delayTaskTpowinien mieć typ (a -> b) -> Number -> a -> TaskT r m b.

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

Jednak setTimeout(comp(k) (f), ms, x)zwraca identyfikator limitu czasu, który nie pasuje do typu m r. Zauważ, że k => setTimeout(comp(k) (f), ms, x)powinno mieć typ (b -> m r) -> m r.

W rzeczywistości niemożliwe jest wyczarowanie wartości typu, m rgdy kontynuacja kjest wywoływana asynchronicznie. ContTTransformator monada działa tylko dla synchronicznych obliczeń.

Niemniej jednak możemy zdefiniować Taskjako wyspecjalizowaną wersję Cont.

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

Tak więc, ilekroć Taskjest obecny w stosie transformatorów monadowych, zawsze będzie u podstawy, tak jak IO.

Jeśli chcesz Taskzabezpieczyć stos monad, przeczytaj następującą odpowiedź .

Kwestia # 2: fooFunkcja ma nieprawidłowy typ zwrotu

Załóżmy na chwilę, że delayTaskTma właściwy typ. Następnym problemem, jak już zauważyłeś, jest fooniewłaściwy typ zwrotu.

Wydaje się, że problem polega na tym, że foopowrót TaskTowinięty w a, Chaina ten owinięty TaskTjest całkowicie odłączony od TaskTłańcucha i dlatego nigdy nie jest oceniany / odpalany.

Zakładam, że oczekiwany typ footo a -> TaskT r Trampoline a. Jednak rzeczywisty typ footo a -> Trampoline (TaskT r m a). Na szczęście naprawa jest łatwa.

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

Rodzaj foojest taki sam jak taskOfTnp a -> TaskT r m a. Możemy się specjalizować m = Trampoline.

Kwestia # 3: Nie używasz taskLiftTpoprawnie

taskLiftTFunkcja podnosi się do podstawowej jednowartościowy obliczenia w TaskTwarstwie.

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

Teraz aplikujesz taskLiftT(recChain)do foo(1)i 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)

Jeśli jednak użyjemy poprawnej definicji, footypy nawet nie będą pasować.

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)

Jeśli używamy poprawnej definicji, foosą dwa sposoby na zdefiniowanie bar. Zwróć uwagę, że nie ma sposobu, aby poprawnie zdefiniować fooużycie setTimeout. Dlatego przedefiniowałem foojako taskOfT.

  1. Używaj fooi nie używaj 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. Nie używaj fooi nie używaj 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] Dlaczego w Haskell nie ma transformatora IO?