Yeah, I got bogged down in monads, but I've promised myself I'll finish it this year. Learn Yourself a Haskell[...] is extremely well-written and entertaining, so it deserves completion.
All the resources I've seen for Haskell start the same
They are insanely easy to follow at the start, so simple that even first time programmers won't have much trouble following. Then the type algebra hits and everything goes out the window.
I've made 3 attempts to get through it. I can even see situations in my work where monads and monoids may very well be applicable. I still can't work with Haskell, nor get through any resource that discusses them.
It's possible I'm an idiot, but I suspect my co-workers would fare worse, and it damns the language in my book.
It's possible I'm an idiot, but I suspect my co-workers would fare worse, and it damns the language in my book.
No, you're definitely not an idiot. Haskell's type system is a completely different language, semantically speaking, from its language of terms. This type language is much more like a logic programming language than a functional one. Type variables are unified by the type inference engine, a concept with which most people are unfamiliar. Type constructors are easily mistaken for data constructors (they often use the same name). A lot of Haskell tutorials gloss over these details which is unfortunate.
Please don't get discouraged. Once you start to grok the type system you'll be amazed at how powerful it is. It's pretty incredible how well it works at inferring all the types and catching countless errors. The oft-cited claim that Haskell programs "just work" once they pass the type-checker is true in the vast majority of cases. The amount of work it saves you by not having to write (and maintain) unit tests is staggering.
I'm familiar with the argument that strict typing catches bugs, but some bugs are trivial and a few are very tricky. Based on my experience with languages besides Haskell (which I don't know), I suspect that the kinds of bugs computers catch automatically aren't likely to be bad ones. The bad ones are runtime errors and logic errors, and I guess there are two kinds of these: ones that come from sloppy coding, and ones where you made an error in thinking. The type-checker might help with the sloppy ones. But I think the best language defense against those is to be concise and readable, so the logic is easy to see and you don't lose the forest for the trees.
That's just the thing: Haskell's type system catches many, many bugs that would be runtime errors in most other languages. It can even provide static guarantees against extremely tricky bugs such as race conditions!
sloppy coding
Everybody is guilty of sloppy coding some of the time. Having a powerful sanity check against it is extremely helpful.
ones where you made an error in thinking
Haskell can actually help here, too. Its rigid purity forces you to think more carefully before you act and its powerful expressiveness enables you to build highly composable abstractions that are simply not possible in other languages.
Edit: Check out this talk on someone's personal experience with static typing in real projects at work:
I very rarely have much trouble finding bugs that I made myself. The hardest bugs are the ones somebody else had a hand in. I'm sure everyone wishes they could force their coworkers to be more careful. :)
In college I was required to take a course using SML (which has a type system that is sort of like Haskell's I think; Haskell's may have been based on it). I was not all that academically driven that semester, and I was kind of taken by surprise by how hard the language was, once I started doing the later projects, and I barely got through the course. That was my only exposure to functional programming until after college, when I wanted to make up for that and I undertook to learn Scheme. I didn't have much trouble with Scheme, and the functional concepts I'd heard about in the course finally made sense. If you're trying to learn the functional style, I don't see the point in learning at the same time a complex type system. The type system isn't a natural consequence of functional programming, it's something that's not really related but adds a lot of difficulty.
I don't want to start a flame war, but I view types as being sort of peripheral to the task of getting the computer to do things. If type systems are getting in the way of programming, priorities are getting mixed up.
Type systems aren't supposed to "get in the way of programming". Instead, they are supposed to assist you, and they do. This is why there is the perception that, for instance, code written in languages like OCaml commonly turns out to be correct once it compiles correctly. It's certainly not the case that language designers deploy a complex type system to make your work harder.
I don't question that they're created with the best intentions, but the most important thing is the end result, not the programming language. And well-intentioned language features don't always work out like they're supposed to.
That's why I love Typed Racket - it's powerful and expressive, yet completely optional and integrates seamlessly with untyped code. It helps that Racket has the best support for contracts I've seen though.
A lot of aspects of programming seem very very difficult until they click. Recursion and pointers are the canonical examples for basic programming. Although I'm still on the same side of learning Haskell as you, I think these things can be grokked by non-geniuses given enough practice and determination.
All of it makes much more sense if you use Haskell more. For example, when you're trying to write some program that works in an inherently stateful fashion, as a beginner you usually pass state as an additional argument to your function, and return it as additional result. Then, when you learn about State monad, everything clicks into place.