Trampoline is a monad and adds stack-safety to a monad transformer stack. It achieves this by relying on a special interpreter (monadRec), which is fed with the result of a monadic computation (actually it is a specialized version of the free monad pattern). For this reason the Trampoline monad must be the outermost monad, that is the base monad of the transformer stack.
In the following setting TaskT (which is essentially Cont with sharing) is the monad transformer and Trampoline the base monad:
// 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
This is not what I want, because the Trampoline forces evaluation before the event loop receives the result of the asynchronous tasks. What I need is the other way around but as I've already mentioned there is no TrampolineT transformer. What am I missing?
There are several issues in this code snippet.
Issue #1: There is no monad transformer for
IO(i.e.Task)It's well known that there is no monad transformer for
IO.[1] YourTaskTtype is modeled afterContT, andContTis indeed a monad transformer. However, you're usingTaskTto perform asynchronous computations such assetTimeout, which is where the problem arises.Consider the definition of
TaskT, which is similar toContT.Hence,
delayTaskTshould have the type(a -> b) -> Number -> a -> TaskT r m b.However,
setTimeout(comp(k) (f), ms, x)returns a timeout id which does not match the typem r. Note thatk => setTimeout(comp(k) (f), ms, x)should have the type(b -> m r) -> m r.In fact, it's impossible to conjure a value of type
m rwhen the continuationkis called asynchronously. TheContTmonad transformer only works for synchronous computations.Nevertheless, we can define
Taskas a specialized version ofCont.Thus, whenever
Taskis present in a monad transformer stack it'll always be at the base, just likeIO.If you want to make the
Taskmonad stack safe then read the following answer.Issue #2: The
foofunction has the wrong return typeLet's assume for a moment that
delayTaskThas the correct type. The next issue, as you have already noticed is thatfoohas the wrong return type.I'm assuming that the expected type of
fooisa -> TaskT r Trampoline a. However, the actual type offooisa -> Trampoline (TaskT r m a). Fortunately, the fix is easy.The type of
foois the same astaskOfT, i.e.a -> TaskT r m a. We can specializem = Trampoline.Issue #3: You're not using
taskLiftTcorrectlyThe
taskLiftTfunction lifts an underlying monadic computation into theTaskTlayer.Now, you're applying
taskLiftT(recChain)tofoo(1)andfoo(2).However, if we use the correct definition of
foothen the types wouldn't even match.If we're using the correct definition of
foothen there are two ways to definebar. Note that there's no way to correctly definefoousingsetTimeout. Hence, I have redefinedfooastaskOfT.Use
fooand don't usetaskLiftT.Don't use
fooand usetaskLiftT.[1] Why is there no IO transformer in Haskell?