I'm currently working on a presentation about Functional Programming, and came upon the following problem.
Functional Programming intends to separate the 'what' from the 'how', or more precisely, the declaration of a computation from its interpretation. This is why one of the main focus of this paradigm is to represent computations using composable data-structures without making any assumptions about how they are performed. For example:
// Represents a computation that may fail
case class Unsafe[A,B](run: A => B)
// ...
val readIntFromFile: Unsafe[String, Int] = Unsafe { filePath => /* ... */ }
interpret(readIntFromFile)
// Interpreter
def interpret(u: Unsafe[String, Int]): Unit = {
try {
u.run("path/to/file")
} catch {
case e => /* ... */
}
}
This seems to make sense, as side-effects should be only performed during the execution of the computation and not during its declaration. The problem is that in Scala, as it seems, many data-structures break this rule:
object Try {
/** Constructs a `Try` using the by-name parameter. This
* method will ensure any non-fatal exception is caught and a
* `Failure` object is returned.
*/
def apply[T](r: => T): Try[T] =
try Success(r) catch {
case NonFatal(e) => Failure(e)
}
}
Same for Futures:
/** Starts an asynchronous computation and returns a `Future` object with the result of that computation.
*
* The result becomes available once the asynchronous computation is completed.
*
* @tparam T the type of the result
* @param body the asynchronous computation
* @param executor the execution context on which the future is run
* @return the `Future` holding the result of the computation
*/
def apply[T](body: =>T)(implicit @deprecatedName('execctx) executor: ExecutionContext): Future[T] = impl.Future(body)
So, I'm wondering now, are Try and Future really referentially transparent? If not, then how should one handle the error cases without relying on Success and Failure?
Try is referentially transparent as long as you don't use side effects. The purpose of Try is not to control side effects, but to handle a possible exception.
If you need to control side effects in a pure way you can use Task or IO types from libraries like Cats and Scalaz.