Jak połączyć TaskT z monadą Trampoline, aby uzyskać niezliczone obliczenia asynchroniczne?
Trampoline
jest 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 Trampoline
monada musi być monadą najbardziej zewnętrzną, czyli monadą podstawową stosu transformatorów.
W następującym ustawieniu TaskT
(które zasadniczo wiąże się Cont
ze współdzieleniem) znajduje się transformator monady i Trampoline
monada 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ż Trampoline
ocena wymuszeń, zanim pętla zdarzeń otrzyma wynik zadań asynchronicznych. Potrzebuję na odwrót, ale jak już wspomniałem, nie ma TrampolineT
transformatora. czego mi brakuje?
Odpowiedzi
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 TaskT
typ jest wzorowany na ContT
i ContT
rzeczywiście jest transformatorem monadowym. Jednak używasz TaskT
do 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 delayTaskT
powinien 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 r
gdy kontynuacja k
jest wywoływana asynchronicznie. ContT
Transformator monada działa tylko dla synchronicznych obliczeń.
Niemniej jednak możemy zdefiniować Task
jako wyspecjalizowaną wersję Cont
.
newtype Task a = Task { task :: (a -> ()) -> () } -- Task = Cont ()
Tak więc, ilekroć Task
jest obecny w stosie transformatorów monadowych, zawsze będzie u podstawy, tak jak IO
.
Jeśli chcesz Task
zabezpieczyć stos monad, przeczytaj następującą odpowiedź .
Kwestia # 2: foo
Funkcja ma nieprawidłowy typ zwrotu
Załóżmy na chwilę, że delayTaskT
ma właściwy typ. Następnym problemem, jak już zauważyłeś, jest foo
niewłaściwy typ zwrotu.
Wydaje się, że problem polega na tym, że
foo
powrótTaskT
owinięty w a,Chain
a ten owiniętyTaskT
jest całkowicie odłączony odTaskT
łańcucha i dlatego nigdy nie jest oceniany / odpalany.
Zakładam, że oczekiwany typ foo
to a -> TaskT r Trampoline a
. Jednak rzeczywisty typ foo
to a -> Trampoline (TaskT r m a)
. Na szczęście naprawa jest łatwa.
const foo = delayTaskT(x => x) (0);
Rodzaj foo
jest taki sam jak taskOfT
np a -> TaskT r m a
. Możemy się specjalizować m = Trampoline
.
Kwestia # 3: Nie używasz taskLiftT
poprawnie
taskLiftT
Funkcja podnosi się do podstawowej jednowartościowy obliczenia w TaskT
warstwie.
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, foo
typy 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, foo
są dwa sposoby na zdefiniowanie bar
. Zwróć uwagę, że nie ma sposobu, aby poprawnie zdefiniować foo
użycie setTimeout
. Dlatego przedefiniowałem foo
jako taskOfT
.
Używaj
foo
i nie używajtaskLiftT
.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
Nie używaj
foo
i nie używajtaskLiftT
.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?