Wie kombiniere ich TaskT mit der Monadeninstanz von Trampoline, um stapellose asynchrone Berechnungen zu erhalten?

Dec 13 2020

Trampolineist eine Monade und erhöht die Stapelsicherheit eines Monadentransformatorstapels. Dies wird erreicht, indem ein spezieller Interpreter ( monadRec) verwendet wird, der mit dem Ergebnis einer monadischen Berechnung gespeist wird (tatsächlich handelt es sich um eine spezielle Version des freien Monadenmusters). Aus diesem Grund Trampolinemuss die Monade die äußerste Monade sein, dh die Basismonade des Transformatorstapels.

In der folgenden Einstellung TaskT(die im Wesentlichen Contmit Sharing verbunden ist) befinden sich der Monadentransformator und Trampolinedie Basismonade:

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

Dies ist nicht das, was ich möchte, da die TrampolineKraftauswertung vor der Ereignisschleife das Ergebnis der asynchronen Aufgaben empfängt. Was ich brauche, ist umgekehrt, aber wie ich bereits erwähnt habe, gibt es keinen TrampolineTTransformator. Was vermisse ich?

Antworten

2 AaditMShah Dec 14 2020 at 12:10

In diesem Code-Snippet gibt es mehrere Probleme.

Problem Nr. 1: Es gibt keinen Monadentransformator für IO(dh Task)

Es ist bekannt, dass es keinen Monadentransformator für gibt IO. [1] Ihr TaskTTyp ist nachempfunden ContTund ContTin der Tat ein Monadentransformator. Sie verwenden TaskTjedoch asynchrone Berechnungen, z. B. setTimeoutwo das Problem auftritt.

Betrachten Sie die Definition von TaskT, die ähnlich ist ContT.

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

Daher delayTaskTsollte der Typ haben (a -> b) -> Number -> a -> TaskT r m b.

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

Allerdings setTimeout(comp(k) (f), ms, x)gibt eine Timeout - ID , die nicht mit dem Typ überein m r. Beachten Sie, k => setTimeout(comp(k) (f), ms, x)dass der Typ haben sollte (b -> m r) -> m r.

Tatsächlich ist es unmöglich, einen Wert vom Typ zu beschwören, m rwenn die Fortsetzung kasynchron aufgerufen wird. Der ContTMonadentransformator funktioniert nur für synchrone Berechnungen.

Trotzdem können wir Taskals spezialisierte Version von definieren Cont.

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

Wenn Taskalso ein Monadentransformatorstapel vorhanden ist, befindet er sich genau wie an der Basis IO.

Wenn Sie den TaskMonadenstapel sicher machen möchten, lesen Sie die folgende Antwort .

Problem Nr. 2: Die fooFunktion hat den falschen Rückgabetyp

Nehmen wir für einen Moment an, delayTaskTder den richtigen Typ hat. Das nächste Problem ist, wie Sie bereits bemerkt haben, dass fooes den falschen Rückgabetyp hat.

Das Problem scheint zu sein, foowelche Rückgabe eine TaskTin a eingewickelte Chainund diese eingewickelte TaskTvollständig von der TaskTKette entkoppelt ist und somit niemals ausgewertet / abgefeuert wird.

Ich gehe davon aus, dass der erwartete Typ von fooist a -> TaskT r Trampoline a. Der tatsächliche Typ von fooist jedoch a -> Trampoline (TaskT r m a). Glücklicherweise ist die Lösung einfach.

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

Die Art von fooist die gleiche wie taskOfT, dh a -> TaskT r m a. Wir können uns spezialisieren m = Trampoline.

Problem Nr. 3: Sie verwenden nicht taskLiftTrichtig

Die taskLiftTFunktion hebt eine zugrunde liegende monadische Berechnung in die TaskTEbene.

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

Jetzt bewerben Sie sich taskLiftT(recChain)bei foo(1)und 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)

Wenn wir jedoch die richtige Definition von verwenden, stimmen foodie Typen nicht einmal überein.

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)

Wenn wir die richtige Definition von verwenden, foogibt es zwei Möglichkeiten zu definieren bar. Beachten Sie, dass es keine Möglichkeit gibt, foousing korrekt zu definieren setTimeout. Daher habe ich neu definiert fooals taskOfT.

  1. Verwenden foound nicht verwenden 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. Nicht benutzen foound benutzen 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] Warum gibt es in Haskell keinen E / A-Transformator?