Error handling in scala - the right functional way


#1

several Internet sources such as:

http://longcao.org/2015/06/15/easing-into-functional-error-handling-in-scala

suggest avoiding throwing exceptions in Scala

what is the best practice (pure functionally speaking) for terminating the application in case of an error if throwing exceptions is not recommended? shall i log the error and use sys.exit(n) anytime i need to terminate the application?

what is the best practice for large application?


#2

Use exceptions for exceptional situations. Rule of thumb, if your application can’t recover from an error, throw an exception and let it crash. If it can recover, then do that.

Examples of recovering are using Option, Either, or other sum types to indicate error situations.

From the functional perspective, exceptions invalidate referential transparency–i.e. you can’t directly substitute a piece of exception-throwing code for the value it could evaluate to, because that would change the meaning of the code. So generally we try fairly hard to not throw exceptions.

An exception (…) is performance bottlenecks. If you’re profiling your code and finding an unacceptable slowdown from creating lots of ‘result’-style objects, you may need to rewrite that part of the code to throw/catch exceptions instead. But, this should be a last resort.


#3

If we’re talking about a monolithic, single-pass application, you would
surround the main application calculation with something that would react
to failure values:

object MyApp {

def main(args: Array[String]): Unit = {
Try {
//…main application calculation…
} match {
case Failure(problem) =>
System.exit(-1);
case _ =>
//…nothing to do here, just let application quit…
}
}
}

But it’s important to realize that you can’t be functional “all the way
down”. The essence of functional programming is that you are expressing
your application as a big functional equation/expression, and by definition
the perfect such expression would have no side-effects. Zero. This mean
you’d also have no I/O, and no permanent side-effects whatsoever.

This dream I think is theoretically impossible, since at a minimum you must
change the quantum state of the universe in some minimal way in order to
produce any form of information that you didn’t already have, and that
quantum state change would be a side-effect. That is, you need to at least
warm up your CPU a tiny bit to compute anything, and that temp diff would
be a side-effect to a purist.

Just be aware that you have to be at least a little bit side-effecting and
non-functional at the edges of your application.

Brian maso


#4

Just a pointer to a similar, but broader discussion from the mailing list some time ago:

https://groups.google.com/d/msg/scala-user/SpSMvIFLY_w/JKeRFAc-ucIJ

Not a “one fits all” solution there, obviously, but lots of food for thought.

Given your specific question, I’d think it just boils down to the necessity of the “news” of the failure having to bubble up to the main method one way or the other. Devising a concrete strategy for which (combination of) vessel(s) to use (Exceptions, Either,…) for this news, and when to switch between them, is the hard part.

sys.exit() won’t let anybody intercept - some piece of code way down in the call stack may decide to pull the plug without warning for everything that’s going on in the system. That would be my very last choice to consider. All the other options just signal that something went wrong, and higher level code might jump in and mitigate the problem, or at least do some cleanup work - until the news ends up in main, which can pull the plug by just returning.


#5

This seems like a no-op. If an exception bubbles up to the main method, it will cause a stack trace and program exit anyway.

More generally, exception handliers should work on as small a slice of code as possible. They should definitely not be wrapping your entire app and catching any possible exception that got thrown.

I think you’ve misunderstood functional programming. It’s perfectly possible to be purely functional and do I/O without side effects. See e.g. Haskell’s IO type, or http://degoes.net/articles/only-one-io


#6

Doing I/O without side effects is a contradiction and the IO Monad doesn‘t make IO pure. It just indicates that there‘s a side effect.


#7

In scala at least your main method or the main method of some IOApp trait that your main object extends has to call unsafePerformIO.


#8

When encountering an exception you have two general options:

1- Recover/Ignore/Handle it (following your use case)
2- Throw it and end the program

In both cases you want to handle/throw it in the most sensible way:

1- Keeping the handling logic contained in a specific and appropiate
functionality module of your code (i.e. one that can send messages to
users/admins)
2- Keep outside world puntually informed (users/admin), generally letting a
program crash is considered a bug

Functional structures like (but not limited to) monads just provide more
appropiate/standarized/easy to reason semantics, as you can pass the
exception around as a value while mantaining your program fully functional
(referentially transparent) until the “end of the world” (main) in which
you allow the side effects to kick in, effectively “opening” your
application to the outside world (unsafePerformIO and company)


#9

Nope. You can do I/O just fine without side effects–you just need to wrap them and turn them into first-class effects. First-class effects make I/O pure.


#10

Yeah, but that doesn’t invalidate functional purity. Haskell essentially does the same thing–it runs the entire program, which is an IO () value, in its runtime.


#11

I/O is impure by definition, no matter how you might wrap the cause of the effect.

Or as Martin Odersky said:

The IO monad does not make a function pure. It just makes it obvious that it’s impure.


#12

Look, what do you mean when you say ‘pure’? What I mean is ‘referentially transparent’, meaning you can substitute an expression for the value it evaluates to, without changing the meaning of the program. And this includes stuff like throwing exceptions.

By this definition you are pure if you control your effects using the IO type (‘monad’ is irrelevant here, it’s just a composition mechanism).


#13

Hello,

What do you mean by “can substitute”? Would you still get the same I/O if
you substitute an expression that creates an IO object by that IO object?


#14

I mean literally substitute directly in the source code. E.g., substitute this:

val result = IO { println("Hi!"); 1 + 1 }

for this:

val result = IO { println("Hi!"); 2 }

IO is deliberately designed so that you can’t type in a literal ‘IO object’, hence I gave examples of evaluating stuff inside IO creation expressions.

If you don’t have that IO wrapper around the printing and then addition, then you are changing the meaning of the program because now it’s a program that can potentially throw exceptions. IO programs by design won’t.


#15

Purely functional means I can substitute an expression by the value it
produces. If I can’t create such a value without the expression, then it is
clearly not purely functional.


#16

You can tough.

Evaluating the same expression giving you something of type IO[A] twice, or passing the result of the expression is the same thing. That’s referential transparency, and something you can’t do with side effects.


#17

Can you create an Option(1) value without writing the expression Option(1)? No. So, you are saying Option(1) is clearly not purely functional?


#18

It terminates the main thread by default, it does not kill the JVM. This becomes important when you have more than one non-daemon thread. In these cases it makes sense to have an explicit exception handler that prints the stack trace and terminates the JVM.