This can be a real problem, for example if you read potentially large network input eagerly and use it to compute a small result lazily. IMO it's always a mistake and IMNSHO the proper solution is always something more nuanced than just "make it eager" or "make it properly lazy".
If things are lazy by default, you simply need to put a "!" before the term to evaluate in order to get eagerness.
If things are eager by default, you need to rewrite the algorithm as a streaming algorithm.
One example would be Eigen: https://eigen.tuxfamily.org/index.php?title=Main_Page
See also their page on lazy evaluation: https://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html
Of course, C++ is powerful enough that using specific tools can make the expression template abstraction fall apart (e.g. auto), but fundamentally you can write matrix code very cleanly and get great lazy-evaluated performance.
There was a good Reddit thread recently where a lot of experts weighed in: https://www.reddit.com/r/haskell/comments/wpbs4z/what_things...
I recommend reading the thread -- lot's of great stuff in there, but...
TL;DR: It's a lot more complicated than it seems at first and it's far from obvious that strict-by-default is worth the cost.
Since pure functions compute the same results under lazy or strict evaluation and require that any data dependencies they have are explicitly provided as inputs, they interact with lazy computations in a much more tractable way. This means that adding a strictness operator to a lazy language is much easier than adding a laziness operator a a strict language.
An alternate approach is what python did with generators where there is a data type for lazy computation, but it lives apart from the rest of the language, so it is mostly used for e.g. stream processing where a default-lazy approach is conceptually straightforward and is less likely to lead to extremely non-trivial control flow. This approach does, however, basically give up on having a laziness operator that will turn a strict computation into a lazy one.
lazy by default enables better composition and local reasoning.
https://publish.obsidian.md/paretooptimaldev/Advantages+of+l...
That's wrong. With the bang pattern, evaluation only goes up to WHNF, not the whole way — it's just a syntax sugar around "seq" which has been around since always. If you need to fully evaluate the term, look at the deepseq package: it's implementation is non-trivial.
Sure, GHC goes to heroic lengths and turns as much lazy computations as it can into eager ones, but there is always a limit to what it can do on its own, and you inevitably end with the programmer having to slap strict patterns and seqs and deepseqs wherever they can reach.
The ergonomics become incredibly bad, unfortunately.
(Also see my link to a Reddit thread elsewhere in this thread further ideas on why strict-by-default is not a simple win.)
I think it might actually have been a mistake to have the lazy variants of these data structures, esp. Text/ByteString. IME it's extremely rare to want laziness for these... and it's usually just when building output strings (where there are better solutions like Builders). Anyway, I digress...
IME the same applies to lazy Map/Set, but I'm less familiar with uses for the lazy versions of these. (IIRC they're only spine-lazy, which is rarely what you want, tho.)
> And in LISP IIRC lazy data are type-compatible with strict data.
LISP is dynamically typed -- it's trivial to do there. Also not the directionality: You can go from lazy to strict (that's just evaluation which the runtime will do for you), but you can NOT go the other way automatically. Recovering laziness from strictness is impossible.
The problem with the split happens when you have mismatching types that your compiler will shout at you for.