However it might make sense in a tutorial or course to teach do-notation first as a DSL for IO in order to get started. And only later, when the student have a good understanding of the core language and type system, introduce monads and show how IO is a monad and how do-notation can be used with other monads.
The problem with the much maligned monad tutorials is that the audience typically is beginners who get stuck on on the IO monad and do-notation immediately after writing "hello world". But the tutorials try to explain monads as a general concept, which requires a pretty through understanding of things like higher kinded types and so on. An understanding the newbie getting stuck at "hello world" obviously doesn't have yet. Then the tutorials try to be accessible by using metaphors like a "monad is a specesuit", a "monad is burrito" etc. But knowing that a monad is a buritto doesn't help the newbie who gets inscrutable compile errors after adding a second putStrLn to "hello world".
The time it takes to produce widget 2 ^ t is BASE_TIME * LEARNING_RATE ^ t.
Example with 120 minute starting time and 90% learning rate:
Widget 1 (2 ^ 0) takes 120 (120 * 0.9 ^ 0) minutes to produce.
Widget 2 (2 ^ 1) takes 108 (120 * 0.9 ^ 1) minutes to produce.
Widget 4 (2 ^ 2) takes 97.2 (120 * 0.9 ^ 2) minutes to produce.
Widget 8 (2 ^ 3) takes 87.48 (120 * 0.9 ^ 3) minutes to produce.
Example with 120 minute starting time and 70% learning rate:
Widget 1 (2 ^ 0) takes 120 (120 * 0.7 ^ 0) minutes to produce.
Widget 2 (2 ^ 1) takes 84 (120 * 0.7 ^ 1) minutes to produce.
Widget 4 (2 ^ 2) takes 58.8 (120 * 0.7 ^ 2) minutes to produce.
Widget 8 (2 ^ 3) takes 41.16 (120 * 0.7 ^ 3) minutes to produce.
If you plotted a curve of minutes per widget over time, you would note that the graph for the 70% learning rate would have a much steeper slope than that of the 90% learning rate.
Looking at that you might say, "ah, high learning rates are great!" All else being equal, you would be correct. However, real world tasks with high learning rates often imply high base times and a proportionately large amount of time until proficiency or mastery is reached. That's why "steep learning curves" are "bad things", because it is going to take "a lot of effort" just to "get good" at the skill.
To bring the point home, here are some contrived examples of "90%" and "70%" skills:
Version control:
90% - copying and pasting files
70% - git
Word processing:
90% - Microsoft Word
70% - vim and latex
Databases:
90% - Excel documents in a shared folder
70% - PostgreSQL
Web design:
90% - your web host's website builder
70% - Ruby on Rails
70% - hand coding in notepad
This is just an introduction to the concept as I learned it. Two skills might have the same learning rate, but one might be much more productive than the other. Still this should help you understand the origin of "steep learning curve" and why that's usually considered a "bad thing."
It makes perfect sense to have do notation and desugaring it to use >>=. For "imperative" programming, do notation makes sense. Other times you'll be writing data processing "pipelines" where using >>= is more convenient. To give an example:
readQueries inFile >>= readFromDb db >>= writeOutput outFile
The above would be very clumsy to write in do notation (but not too difficult, that's what you'd do in an imperative language). But writing "guess the number" without do notation is also clumsy (but a good learning exercise).do notation and monad laws may be a foreign concept when coming from an imperative programming background, but it's also a neat an elegant concept that works very well in practice.
It sure did not take "no time," and much longer than an hour - longer than any other programming language I have attempted. The biggest stumbling blocks were syntactical. For example, there are three ways to declare a variable (let-in, let, and <-) and if you use the wrong one, you get a very unhelpful error message. The learning was not so much new concepts but instead things like how a "not in scope" error means you used a tab instead of spaces.
I completely agree, the underlying concepts are elegant, and powerful and enlightening and quite amazing. But you can understand the concepts perfectly and still not be able to write a line of Haskell. That's where most tutorials fall short.
I certainly remember having a hard time understanding this years ago, and my problem was probably the same as yours - the lack of nice books and tutorials (but things are a bit better now) as well as it all being new and foreign. I don't regret putting in the effort, though.
I also do not like the inconsistency in let-in vs. let (do notation), but I guess there's a good reason for that. <- is not strictly "variable assignment" but it seems superficially similar, though (if comparing to imperative languages).
But I don't think these issues can be "fixed" without turning Haskell into something completely different (e.g. something more like ML). Or do you have a good suggestion how to simplify this without making it worse?
“I have to directly use the result of one IO action in order to decide which IO action should happen next”: Yes, this is a use case for IO’s monadic interface."
Then yes - you need the do-notation.
For simple sequencing he recommends to use it's Applicative instance.
Your overall point is correct though - choosing to avoid the do notation in Haskell will likely limit your ability to read and understand a lot of code just because monads are so commonplace in Haskell.
The rebuttal is do-notation. The second program you write after Hello World is going to use two IO actions instead of one, and so you need a way to sequence them, and every tutorial is going to do that with do-notation. And suddenly all of the other syntax you learned, like how to declare a variable with let-in, or how arrows go -> that way, or how to call a function, is tossed out the window. And you'll spend ten minutes before you realize your indentation is wrong and then ten more fighting with how you declare a variable and then apply a function to it before it dawns on you, this is monads.
You don't have to use do-notation, sure. You can use >>= as an infix operator. It's still totally confusing.
The reality is that monads hit you hard and fast as soon as you want to do any IO. Not the monad laws or functors or whatever the tutorials focus on, but the nitty-gritty syntax. This is where I got stuck and gave up (twice), before I finally got it.