Thanks for the discussion!
In a typed language, it does seem like a lie to say “this function returns an integer” but then it throws an exception. I remember working in Haskell, there was a standard function called
head that would give you the first element of a list. But what if the list was empty? It would throw an error. That was annoying behavior in that environment. It was obvious that it should never throw an error and instead return a Maybe. We define a function that did just that.
I think avoiding throwing exceptions and avoiding using nulls can improve maintainability. Exceptions mean you branch to handle them (the catch block of the try/catch). And nulls need to be checked (also a branch). These are branches that are not explicitly part of the model (represented by a type in certain languages). Exceptions and nulls are usually corner cases, which you typically want to avoid. They mean more code than necessary. Without corner cases, things compose more cleanly.
You can do the same in Scala, and many people do. And as he mentioned, you can do everything but nulls in Haskell. What Clojure doesn’t have that Scala and Haskell do are explicit calling out of effects.
I’ve heard of people being in favor of never throwing exceptions, especially for things that should be predictable. For instance, reading in a file that doesn’t exist. Is that truly exceptional? No, it’s actually common. It should be part of the explicit failure modes. True exceptions are rare, such as a failure of the runtime. OutOfMemoryError might be one of them.