I'm currently studying lazy evaluation in conjunction with monads in Javascript and what use cases may evolve from these. So I tried to implement a lazy type, which implements the functor/monad type class. The corresponding constructor is meant to be lazy in its arguments and in its result. Here is what I came up with:
// a lazy type
// (() -> a) -> () -> b
const Lazy = thunk => () => thunk();
// (b -> a -> b) -> b -> Lazy a -> b
Lazy.fold = f => acc => tx => f(acc) (tx());
// (a -> b) -> Lazy a -> Lazy b
Lazy.map = f => tx => Lazy(() => f(tx()));
// Lazy (a -> b) -> Lazy a -> Lazy b
Lazy.ap = tf => tx => Lazy(() => tf() (tx()));
Lazy.of = Lazy;
// Lazy (Lazy a) -> Lazy a
Lazy.join = ttx => ttx();
// (a -> Lazy b) -> Lazy a -> Lazy b
Lazy.chain = ft => tx => Lazy.join(Lazy.map(ft) (tx));
// recursive bind (or chain in Javascript)
// Number -> (a -> b) -> a -> Lazy b
const repeat = n => f => x => {
const aux = m => y => m === 0
? Lazy(() => y)
: Lazy.chain(aux(m - 1)) (Lazy(() => f(y)));
return aux(n) (x);
};
// impure function to observe the computation
const inc = x => (console.log(++x), x);
// and run
console.log(repeat(5) (inc) (0)); // logs 1, 2, 3, 4, 5, () => thunk()
Now this evidently makes no sense, since the action sequence isn't lazy at all. Lazy.join simply triggers the evaluation prematurely. Hence, the following questions have emerged:
- are sequences of monadic actions in Haskell always eagerly evaluated?
- is lazy evaluation an effect that cannot be implemented by a monad in a strictly evaluated language?
I am not even sure if my research makes any sense, so feel free to vote for closing this question.