Wie kombiniere ich TaskT mit der Monadeninstanz von Trampoline, um stapellose asynchrone Berechnungen zu erhalten?
Trampoline
ist 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 Trampoline
muss die Monade die äußerste Monade sein, dh die Basismonade des Transformatorstapels.
In der folgenden Einstellung TaskT
(die im Wesentlichen Cont
mit Sharing verbunden ist) befinden sich der Monadentransformator und Trampoline
die 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 Trampoline
Kraftauswertung 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 TrampolineT
Transformator. Was vermisse ich?
Antworten
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 TaskT
Typ ist nachempfunden ContT
und ContT
in der Tat ein Monadentransformator. Sie verwenden TaskT
jedoch asynchrone Berechnungen, z. B. setTimeout
wo 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 delayTaskT
sollte 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 r
wenn die Fortsetzung k
asynchron aufgerufen wird. Der ContT
Monadentransformator funktioniert nur für synchrone Berechnungen.
Trotzdem können wir Task
als spezialisierte Version von definieren Cont
.
newtype Task a = Task { task :: (a -> ()) -> () } -- Task = Cont ()
Wenn Task
also ein Monadentransformatorstapel vorhanden ist, befindet er sich genau wie an der Basis IO
.
Wenn Sie den Task
Monadenstapel sicher machen möchten, lesen Sie die folgende Antwort .
Problem Nr. 2: Die foo
Funktion hat den falschen Rückgabetyp
Nehmen wir für einen Moment an, delayTaskT
der den richtigen Typ hat. Das nächste Problem ist, wie Sie bereits bemerkt haben, dass foo
es den falschen Rückgabetyp hat.
Das Problem scheint zu sein,
foo
welche Rückgabe eineTaskT
in a eingewickelteChain
und diese eingewickelteTaskT
vollständig von derTaskT
Kette entkoppelt ist und somit niemals ausgewertet / abgefeuert wird.
Ich gehe davon aus, dass der erwartete Typ von foo
ist a -> TaskT r Trampoline a
. Der tatsächliche Typ von foo
ist jedoch a -> Trampoline (TaskT r m a)
. Glücklicherweise ist die Lösung einfach.
const foo = delayTaskT(x => x) (0);
Die Art von foo
ist die gleiche wie taskOfT
, dh a -> TaskT r m a
. Wir können uns spezialisieren m = Trampoline
.
Problem Nr. 3: Sie verwenden nicht taskLiftT
richtig
Die taskLiftT
Funktion hebt eine zugrunde liegende monadische Berechnung in die TaskT
Ebene.
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 foo
die 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, foo
gibt es zwei Möglichkeiten zu definieren bar
. Beachten Sie, dass es keine Möglichkeit gibt, foo
using korrekt zu definieren setTimeout
. Daher habe ich neu definiert foo
als taskOfT
.
Verwenden
foo
und nicht verwendentaskLiftT
.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
Nicht benutzen
foo
und benutzentaskLiftT
.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?