I am interested to experiment with Haskell-like IO monads in my JavaScript function compositions.
Something like Folktale has Task seems similar to Haskell's IO in that it's lazy, and thus technically pure. It represents an action that can occur in the future. But I have several questions.
How does one form a composition of functions when all the latter functions depend on the return value of the initial impure function in the composition? One has to run the actual Task first, implicitly passing the returned data to the functions further down the line. One can't just pass an unresolved Task around to do anything useful, or can one? It would look something like.
compose(doSomethingWithData, getDataFromServer.run());
I'm probably missing something critical, but what's the advantage of that?
A related question is what specific advantage does lazy evaluation of an impure function have? Sure, it provides referential transparency, but the core of understanding the problem is the data structure that's returned by the impure function. All the latter functions that are piped the data depend on the data. So how does the referential transparency of impure functions benefit us?
EDIT: So after looking at some answers, I was able to easily compose tasks by chaining, but I prefer the ergonomics of using a compose function. This works, but am wondering if it's at all idiomatic for functional programmers:
const getNames = () =>
task(res =>
setTimeout(() => {
return res.resolve([{ last: "cohen" }, { last: "kustanowitz" }]);
}, 1500)
);
const addName = tsk => {
return tsk.chain(names =>
task(resolver => {
const nms = [...names];
nms.push({ last: "bar" });
resolver.resolve(nms);
})
);
};
const f = compose(
addName,
getNames
);
const data = await f()
.run()
.promise();
// [ { last: 'cohen' }, { last: 'kustanowitz' }, { last: 'bar' } ]
Then, another question, perhaps more related to style, is now we have to have composed functions that all deal with tasks, which seems less elegant and less general than those that deal with arrays/objects.
How can we express Haskell's IO type in Javascript? Actually we can't, because in Haskell IO is a very special type deeply intertwined with the runtime. The only property we can mimic in Javascript is lazily evaluation with explicit thunks:
Usually lazy evaluation is accompanied by sharing but for the sake of convenience I leave this detail out.
Now how would you compose such a type? Well, you need to compose it in the context of thunks. The only way to compose thunks lazily is to nest them instead of calling them right away. As a result you can't use function composition, which merely provides the functorial instance of functions. You need the applicative (
ap
/of
) and monad (chain
) instances ofDefer
to chain or rather nest them.A fundamental trait of applicatives and monads is that you can't escape their context, i.e. once a result of your computation is inside an applicative/monad you can't just unwrap it again.* All subsequent computations have to take place within the respective context. As I already have mentioned with
Defer
the context are thunks.So ultimately when you compose thunks with
ap
/chain
you build a nested, deferred function call tree, which is only evaluated when you callrunDefer
of the outer thunk.This means that your thunk composition or chaining remains pure until the first
runDefer
invocation. This is a pretty useful property we all should aspire to.*you can escape a monad in Javascript, of course, but than it isn't a monad anymore and you lose all the predictable behavior.