> You could do lazy eval in non-lazy-by-default langs by using something akin to `Promisses` in JavaScript.
In a strict language, a lazy thunk can be represented as a single mutable cell. In ML, thanks to type abstraction, the mutation is confined to the module that implements laziness, and, in the rest of the program, there's no way to tell that anything impure is happening.
> To my understanding the module system of Haskell is not very limiting at all.
Even Haskell's own designers admit otherwise. Haskell actually originated as an attempt to standardize a lazy, purely functional language for the research community. Modules and records weren't very high in the priority list, and it really shows. But sometimes behind a black cloud there's a silver lining, and the limitations of records inspired Haskell programmers to invent lenses, which are very, very, very awesome.
But, to this day, Haskell still doesn't have a decent alternative to ML-style modules and functors. As far as I can tell, in vanilla Haskell (no extensions), you can only encode functors on modules with a single type component. With either type families or multiparameter type classes, you can encode modules with more than one type component, but the result is very awkward.