I really wish Haskell weren't lazy by default. Laziness is great in places, allows you to write very declaratively, and even allows for some computations that would otherwise be impossible or less efficient. However, most of the time, you envision and write code as a series of steps (even though theoretically in a functional language, you're just writing an expression). In a practical matter, the cases in which lazy evaluation becomes desirable exist, but are limited. Most of the time, you want, and expect when you write it, your code to be evaluated strictly. Of course you can enforce strictness manually with `seq` and bang notations and the like, but this clutters up your code and might not even matter if you're using library code which doesn't do the same. IMO Haskell would greatly served by having laziness be opt-in, not opt-out. But its pretty ingrained at this point, and there doesn't seem to be too much of a push to change it (who knows how much extant code would be broken by such a change).
> However, most of the time, you envision and write code as a series of steps
Perhaps counterintuitively, I disagree here. I think using laziness enough will eventually change your mind that most code must be thought of in such discrete steps. Instead, I tend to keep in mind and think constantly about how a computation might be partially delivered and how it might be consumed. This is part of the declarative promise of laziness playing out, I feel.
Ultimately I don't think there's a "right" decision. I'm comfortable in strict and lazy languages. I do think everyone should experience both strict and lazy evaluations enough to no longer be worried about them and instead just see each as another, separate form of computation. At the very least it'll make you much more aware of what evaluation order and normalization feel like.
(I'd also highly recommend everyone plays with a total language for a while, too. That feel is quite distinct.)
At the end of the day, I feel like having laziness by default and picking around for places which ought to be strict is very dual to having strictness be default and picking around for places to make lazy. Both are pretty annoying.
I do think people are generally more sensitive to problems of excessive laziness than they are to problems of excessive strictness. Manually wiring around stopping criteria and consumption control through all of your (no longer decomposable) loops/recursion is the price of strictness. Both problems are helped by tooling, as well.
It certainly doesn't need to be thought of as a series of discrete steps, and laziness supports that view. Laziness is part of the Haskell philosophy of staying high-level: so high level that you're not even telling the machine in what order to perform its computations. However I stand by my contention that most of the time, when we as programmers write code to be executed, we're envisioning it as a series of steps: this is a major reason why monadic code is so popular and why Haskell's designers invented do-syntax to imitate imperative code. You say that it would be just as annoying to specify the individual places to make lazy, but I struggle to agree with that. After all, the vast majority of programming languages have strictness by default (even other functional languages like SML or Idris) and don't present any difficulty to the programmer. On the other hand, getting around Haskell's default laziness is a constant issue for performance-minded code (from what I've seen; certainly it's a constant topic of conversation), and makes stack traces and debugging hard.
To give a specific example, strictness is a pain in the butt when you try to write a combinator library. I have once tried to write a Parsec clone for Ocaml, and sometimes, I had to write functions in eta-expanded form, lest I enter an infinite recursion.
tel is right. We are desensitized to the problems of strict evaluation. It's the default, so we accept it as a fact of life. Lazy evaluation on the other hand, is "new", so whatever problems it have are magnified by our unwillingness to handle new problems. This is made even worse when we try to use lazy evaluation the same way we use strict evaluation: you get even more problems, and none of the advantages.
I think people are desensitized to the downsides of strict code. I don't think that either is "better", but I appreciate having spent a lot of time working in a lazy-by-default language.
I think the best option is total-by-default, honestly. I really like how Idris works, for instance. If you must work with some code which is non-normalizing then your choice of laziness/strictness can be a major architectural one—like organizing the entire pipeline of your program around a lazy consumer stream/set of coroutines.
Idris, one of the candidates to be "the next Haskell", is strict by default. It's not a mature language (+ecosystem) yet, but certainly usable and interesting. If you like exploring new cool concepts, you should have a look at it! (Plus it's very easy to learn when you know Haskell already.)
Idris also makes it trivial to annotate laziness, something most languages are lacking, also because it is total by default the evaluation strategy doesn't actually matter for all the total code.
Yeah, Idris really seems like a cool language. I wish it was better documented, at both the usage- and implementation-level. The source code for example is woefully under-commented; a quick grep of the source shows about one line of comments for 15 lines of code.
I think you are generalizing a bit too much. You may very well write code that way, but I don't. Having come to haskell from several years with ocaml, I found the switch to lazy by default to be entirely irrelevant and inconsequential. As long as I have the ability to be strict when I need to, and lazy when I need to (which both languages give me), I don't care which is the default.