Search code examples
scalafunctional-programmingmonadsscalaz

How do error handling monads like Eithers achieve referential transparency?


From reading about FP, my understanding of the benefits of removing side-effects is that if all our functions are pure/have referential transparency (something that can only be achieved without side-effects), then our functions are easier to test, debug, reuse, and are more modular.

Since exceptions are a form of side-effect, we need to avoid throwing exceptions. Obviously we still need to be able to terminate processes when something goes wrong so FP uses monads to achieve both referential transparency and the ability to handle exceptions.

What I'm confused about is how exactly monads achieve this. Suppose I have this code using scalaz

def couldThrowException: Exception \/ Boolean = ??
val f  = couldThrowException()
val g = couldThrowException()

Since couldThrowException may return an exception, there is no gaurantee that f and g will be the same. f could be \/-(true) and g be a -\/(NullPointerException). Since couldThrowException can return different values with the same input, it is not a pure function. Wasn't the point of using monads to keep our functions pure?


Solution

  • f() and g() should evaluate to the same value, given the same input.

    In pure FP a function with no arguments must necessarily evaluate to the same result every time it's called. So it's not pure FP if your couldThrowException sometimes returns \/-(true) and sometimes -\/(NullPointerException).

    It makes more sense to return an Either if couldThrowException takes a parameter. If it's a pure function, it will have referential transparency, so some inputs will always result in \/-(true) and some inputs will always result in -\/(NullPointerException).

    In Scala you may well be using a function that is not pure, and not referentially transparent. Perhaps it's a Java class. Perhaps it's using a part of Scala that's not pure.

    But I guess you're interested in bridging between the pure FP world and impure libraries. A classic example of this is IO. println could fail for all kinds of reasons - permissions, filesystem full, etc.

    The classic way to handle this in FP is to use an IO function that takes a "world" state as an input parameter, and returns both the result of the IO call, and the new "world" state. The state can be a "fake" value backed by library code in an impure language, but what it means is that each time you call the function, you're passing a different state, so it's referentially transparent.

    Often, a monad is used to encapsulate the "world".

    You can find out a lot about this approach by reading about Haskell's IO monad.

    Core Scala IO isn't wholly pure, so println might throw and exception, and hence, as you've spotted, isn't fully referentially transparent. Scalaz provides an IO monad similar to Haskell's.

    One thing to note, because it trips up a lot of beginners: there's nothing about the "World" approach that requires a monad, and IO isn't easiest the monad to look at when first learning what a monad is and why they're useful.