scalafunctional-programmingside-effectsreferential-transparency

Is Scala's Try referentially transparent?


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?


Solution

  • 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.