If Haskell's "runtime exceptions" only existed to abort the program, and you couldn't catch them, then I'd agree that encoding them in the type doesn't seem necessary. However, since you can catch them, and often do need to, encoding them in the type would help.
In practice, this means that if you have code that should either act on a valid value or just return the error if given an error, you don't have to write anything special at all. A concrete example: let's imagine yourFunc needs to get a bunch of values that could be errors; you could write code like this:
do a <- couldError "1"
b <- couldError "2"
c <- couldError "3"
return [a, b, c]
This will then work much like an exception: if either a, b or c are an error, the end result will be that error; if they are all normal values, the end result will be a normal value. You only ever have to pattern match when you want to handle errors in some different way and extract the normal type from the Either type. This is exactly the same as a catch statement for exceptions.Since these types are first-class citizens, it's also really easy to add other functions to operate on them. For example, you can use the existing <|> operator to alternate between possible cases.
result = one <|> two <|> three <|> Left "all failed"
(Left is the part of Either that is used to hold errors; the reason it isn't named error is that Either can actually be used for other sorts of control flow as well.) This code will return the first of one, two, three that is not an error or give you "all failed" as the error.Another useful function is `optional`. This does exactly what it sounds like: it allows you to mark some particular function as "optional", so that if it errors out that error gets ignored.
I think code like this is clearer and easier to read than the alternative with exceptions would be.
However, the important part is not the particular functions, but that you can add your own. Both <|> and optional are library functions that are not even specific to error handling--they work on a bunch of other types like parsers.
I think having error handling be first class like this is very useful and makes writing code with it much nicer.
In Haskell, there is a parallel to this: things like error and undefined are much like runtime exceptions. They are what you would use for conditions that should never come up like division by 0 or infinite loops. You wouldn't want to have to deal explicitly with potential errors every time you used division--it would be useless in most cases. The default behavior with these is to immediately abort.
Haskell also has a parallel to checked exceptions: Either (also Maybe and ErrorT and probably others). If a function returns an Either value, it forces you to check whether the result was valid using the type system, just like a checked exception. So `int f(int a) throws SomeError` can be written as `Int -> Either SomeError Int` in Haskell.
The Haskell approach actually has some advantages, mainly stemming from the fact that Either is not baked into the language. This means the type system can be simpler: in Java, you have both the return value and the "effect" (e.g. throws), which are different concepts with the latter baked into the language and inflexible; in Haskell, you just have a different return type that is defined in a library. This also gives you greater flexibility: you can easily encode your own "effect" by defining your own type, and it can behave in the same way as Either.
There are also IO exceptions, but I haven't actually used them for anything, so I can't say much about them.