That's the difference between an "intentional bug" and an "unintentional bug".
The former is when you want the program to do X, and X itself is wrong. Sure, defending against this is rarely possible. The latter is in my experience far more common. When you want the program to do X, but you actually told it to do Y.
Haskell catches most of the latter cases.
One of the main reasons is the functional purity. Because return values are the only result of a function, and these values are type-checked against a specification, it is much harder to do the wrong thing.
For example, an imperative program will idiomatically have methods whose sole effect is mutating their arguments or the objects. Even with a type system -- if you just omit the effects, the compiler really has no way to know that you meant the effects to happen, and you will simply have a bug. A purely functional program will idiomatically use a different style. Instead of mutating arguments and objects, it will return a new value. If you forget to return a new value, the compiler will catch that.
Additionally, the types in Haskell are generic by default. The more generic the type of a function, the more restricted is what that function can do. The restrictions narrow the space of wrong things you can do.
Another interesting property of Haskell is where it chooses to be on the "restrictiveness+guarantess vs. powerful+unguaranteed" trade-off.
It seems many don't even notice that the other side of the "power" coin is "guarantees". The more powerful a construct is -- the less you can say about what it will do. In Haskell, the purity and generic types of functions are two things that heavily restrict code. But there are many other mechanisms to restrict rather than empower code. In many contexts, restrictiveness is considered a bad thing, but in Haskell, this restrictiveness is very useful for reasoning about code.
For example, the function of type: (forall a. a -> a) in Haskell can only be the identity function. It cannot print "hello world", and it cannot mutate variables. These restrictions are useful because we now can have useful laws. For example: id . f = f . id = f. And we can derive these useful laws from the type itself, without even looking at the code!
By helping us find the most restrictive sub-language that can express the solution, we rule out even more bugs.
The power/guarantee trade off isn't a fundamental one, even though in current languages you often have to choose between the two. Creating languages that combine the power of untyped languages with the guarantees of static languages is an active field of research. For example, dependently typed languages, while currently too unwieldy for everyday use, show promise of having both power and guarantees much stronger than Haskell. Now a third, ill-defined axis of "usability" comes into play.
"The power/guarantee trade off isn't a fundamental one, even though in current languages you often have to choose between the two."
It is on the Pareto frontier of the two. Which verges on tautological, but it's still a useful observation. If you can trivially extend the power of a construct while maintaining the same guarantees, the construct was simply broken, and extending the guarantees of a construct without affecting its power means simply that the guarantees were excessively conservative in the first place.
(This can almost be copied and pasted any time anybody claims two properties of a program aren't in conflict, the most common probably being security and ease of programming.)
We may be wrong about where the frontier is... but I doubt we've very wrong.
I may be alone, but I actually feel like I lose power when using say dynamic languages. There is less power of expression. Sure I can do whatever I want, but in reality programming is communication, and I find them much less powerful in that respect.
So using an untyped language only to run an analysis on it later doesn't really seem all that appealing to me, even if it could somehow provide the guarantees of Haskell or dependently typed languages.
Defining them as "intentional bugs" doesn't seem particularly accurate or useful.
Some of them may class as "design" rather than "coding" bugs but they still aren't usually what the programmer intended, and I would say that the majority of production defects that I see in our systems these days would fall into this category.
The former is when you want the program to do X, and X itself is wrong. Sure, defending against this is rarely possible. The latter is in my experience far more common. When you want the program to do X, but you actually told it to do Y.
Haskell catches most of the latter cases.
One of the main reasons is the functional purity. Because return values are the only result of a function, and these values are type-checked against a specification, it is much harder to do the wrong thing.
For example, an imperative program will idiomatically have methods whose sole effect is mutating their arguments or the objects. Even with a type system -- if you just omit the effects, the compiler really has no way to know that you meant the effects to happen, and you will simply have a bug. A purely functional program will idiomatically use a different style. Instead of mutating arguments and objects, it will return a new value. If you forget to return a new value, the compiler will catch that.
Additionally, the types in Haskell are generic by default. The more generic the type of a function, the more restricted is what that function can do. The restrictions narrow the space of wrong things you can do.
Another interesting property of Haskell is where it chooses to be on the "restrictiveness+guarantess vs. powerful+unguaranteed" trade-off.
It seems many don't even notice that the other side of the "power" coin is "guarantees". The more powerful a construct is -- the less you can say about what it will do. In Haskell, the purity and generic types of functions are two things that heavily restrict code. But there are many other mechanisms to restrict rather than empower code. In many contexts, restrictiveness is considered a bad thing, but in Haskell, this restrictiveness is very useful for reasoning about code.
For example, the function of type: (forall a. a -> a) in Haskell can only be the identity function. It cannot print "hello world", and it cannot mutate variables. These restrictions are useful because we now can have useful laws. For example: id . f = f . id = f. And we can derive these useful laws from the type itself, without even looking at the code!
By helping us find the most restrictive sub-language that can express the solution, we rule out even more bugs.