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] YourTaskT
type is modeled afterContT
, andContT
is indeed a monad transformer. However, you're usingTaskT
to perform asynchronous computations such assetTimeout
, which is where the problem arises.Consider the definition of
TaskT
, which is similar toContT
.Hence,
delayTaskT
should 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 r
when the continuationk
is called asynchronously. TheContT
monad transformer only works for synchronous computations.Nevertheless, we can define
Task
as a specialized version ofCont
.Thus, whenever
Task
is present in a monad transformer stack it'll always be at the base, just likeIO
.If you want to make the
Task
monad stack safe then read the following answer.Issue #2: The
foo
function has the wrong return typeLet's assume for a moment that
delayTaskT
has the correct type. The next issue, as you have already noticed is thatfoo
has the wrong return type.I'm assuming that the expected type of
foo
isa -> TaskT r Trampoline a
. However, the actual type offoo
isa -> Trampoline (TaskT r m a)
. Fortunately, the fix is easy.The type of
foo
is the same astaskOfT
, i.e.a -> TaskT r m a
. We can specializem = Trampoline
.Issue #3: You're not using
taskLiftT
correctlyThe
taskLiftT
function lifts an underlying monadic computation into theTaskT
layer.Now, you're applying
taskLiftT(recChain)
tofoo(1)
andfoo(2)
.However, if we use the correct definition of
foo
then the types wouldn't even match.If we're using the correct definition of
foo
then there are two ways to definebar
. Note that there's no way to correctly definefoo
usingsetTimeout
. Hence, I have redefinedfoo
astaskOfT
.Use
foo
and don't usetaskLiftT
.Don't use
foo
and usetaskLiftT
.[1] Why is there no IO transformer in Haskell?