Responding to lutzh:
Thanks a lot for the detailed and insightful answer!
> I expect the answers you'll get to this question will reflect different
> opinions on programming style. Some will say you should never throw
> exceptions, as that breaks referential transparency, others will say
> throwing exceptions is ok.
I'm aware that there is not - cannot be, I guess ;) - a universally
accepted style. I'm looking for - ideally consistent - approaches that
are based on some experience "from the trenches" and work well for some
team/project that I can use as a basis to derive my own.
> The "Functional Programming in Scala" -
http://www.manning.com/bjarnason/ -
> book is clearly against using exceptions:
>
> "As we just discussed, exceptions break Referential Transparency and
> introduce context dependence, moving us away from the simple reasoning of
> the substitution model and making it possible to write confusing
> exception-based code. This is the source of the folklore advice that
> exceptions should be used only for “error handling,” not for “control
> flow.”
>
> I personally think the focus on referential transparency is not too helpful
> when discussing exceptions. You could argue that it actually is
> referentially transparent, as exception throwing equals evaluation to the
> bottom type, which is a legal result. Indeed a "throw" expression in Scala
> will be inferred to be of type "Nothing".
Ok, fair enough.
> And in practice, on the JVM, exceptions are a "fact of life", they'll
> always be there. They indicate abnormal termination, and if my program
> terminates abnormally, referential transparency might not my main concern.
> Also, if Try is considered the functional alternative, that will not
> contain for example an OutOfMemoryError, so you'll still have to reason
> about the exception case.
Good point. But one could probably see those (abnormal program
termination, OOME) as only covering the Java unchecked exception space,
leaving the discussion still open for checked/expected/recoverable
exceptions...?
That's a discussion where I'm not sure where I stand, yet. Exceptions
feel like a part of the method contract to me, and in a statically typed
language it seems natural to encode them in the type system.
There's the databricks style guide that recommends using (unchecked)
exceptions and document thoroughly:
https://github.com/databricks/scala-style-guide#exception
That feels a bit strange to me, as it comes with the usual downsides of
comments: They're not compiler-checked, they'll get out of sync with the
actual code sooner or later, and, most notably: Nobody will read them.
;) So in general I think there's something to be said for encoding
exceptions as part of the method contract within the language/type
system rather than through documentation conventions. But, coming from
Java, of course I'm aware of the downsides of checked exceptions as they
exist there.
Now I'm wondering if exceptional values encoded within the type system
(Try, Either) impose the same problems as classical checked exceptions.
There's the "criticism of checked exceptions" section in the "Exceptions
debate" linked from the Terse Systems blog post:
http://www.ibm.com/developerworks/java/library/j-jtp05254/index.html
Most of the points listed there ("expose implementation details",
"unstable signatures", "swallowing") don't seem to apply (fully) to the
Try/Either approach: Try doesn't encode the concrete exception type, nor
did most usages of Either I've seen, and swallowing is much harder with
those, too. This only leaves the "unreadable code" and "too much
wrapping" points, where "unreadable" is in the eye of the beholder, and
the "wrapping" turns to functional/monadic combinator usage.
> So my style advice, and as it is about style, it'll always be a matter of
> opinion, is:
>
> - Do throw exceptions for abnormal termination
> - Do use Try to catch exceptions, on the caller side, for nice chaining
> - Avoid using Try as the return type
> - Read "
https://tersesystems.com/2012/12/27/error-handling-in-scala/"" and
> follow the advice there.
Just to make sure I understand correctly: The 2nd and 3rd would imply
that in order to "intercept" an exception (e.g. to trigger a side effect
like a rollback, or to convert to some different exception), you'd use a
Try block and then re-throw an exception from there?
How does the "Use Either to report expected failure" advice from the
blog post blend in? And how would you integrate APIs like scala-arm into
this concept?
Thanks again for the link - will have to meditate over this a bit more.
Best regards,
Patrick