I was addressing your specific assertion with regard to encoding error conditions as reserved values in your return type's codomain, which I agree is ugly.
One benefit of not throwing an exception is that you can maintain referential transparency, which exceptions generally break. You can't substitute a function call with its value because throwing an exception will have different effects depending on the context, which consequently hinders the composability of such functions.
Exceptions are also generally not type-safe, e.g. the return type of a function tells you nothing about what exceptions it may throw. If you go the checked exception route like Java, you do get some degree of type safety, but you do so at the expense of higher-order functions, which can't reasonably be expected to know about the specific exception types its arguments may throw.
On the other hand, ADTs are composable. They work for functions that aren't defined for some inputs (maybe an input type that can't be constrained by the type system). They avoid bugs because they force the caller to deal with the exceptional case (unlike returning null, a sentinel value, or throwing a RuntimeException), but without the boilerplate of exceptions, particularly in languages with pattern matching. The caller gets to decide when, if, and how to handle the exceptional case.
Thanks for the thorough explanation. I agree that ADTs are less ugly due to reduced boilerplate and the composability, if your language of choice has exhausting pattern matching (I really don't think "I don't care if my program works, let it crash" is a good default, so non-exhaustive pattern matching is out for me).
Exceptions must be handled somewhere. Out of band error values are dropped on the floor by default, which is very convenient (if you don't care whether your software works), and many programmers take advantage of it (without realizing they have done so).
I'm not sure what you mean by error values being dropped on the floor by default. In Go or languages with ADTs you would have to consciously ignore an exception. Languages where pattern matching is prevalent force you to deal with the exceptional case to unpack anything useful from an Either for instance.
And if the study is to be believed, checked exception are almost always dropped on the floor, just with more boilerplate and ceremony.
One benefit of not throwing an exception is that you can maintain referential transparency, which exceptions generally break. You can't substitute a function call with its value because throwing an exception will have different effects depending on the context, which consequently hinders the composability of such functions.
Exceptions are also generally not type-safe, e.g. the return type of a function tells you nothing about what exceptions it may throw. If you go the checked exception route like Java, you do get some degree of type safety, but you do so at the expense of higher-order functions, which can't reasonably be expected to know about the specific exception types its arguments may throw.
On the other hand, ADTs are composable. They work for functions that aren't defined for some inputs (maybe an input type that can't be constrained by the type system). They avoid bugs because they force the caller to deal with the exceptional case (unlike returning null, a sentinel value, or throwing a RuntimeException), but without the boilerplate of exceptions, particularly in languages with pattern matching. The caller gets to decide when, if, and how to handle the exceptional case.