Search code examples
scalafunctional-programmingmonads

Why is Future considered to be "not referentially transparent"?


So I was reading the "Scala with Cats" book, and there was this sentence which I'm going to quote down here:

Note that Scala’s Futures aren’t a great example of pure functional programming because they aren’t referentially transparent.

And also, an example is provided as follows:

val future1 = {
  // Initialize Random with a fixed seed:
  val r = new Random(0L)
  // nextInt has the side-effect of moving to
  // the next random number in the sequence:
  val x = Future(r.nextInt)
  for {
    a <- x
    b <- x
  } yield (a, b)
}
val future2 = {
  val r = new Random(0L)
  for {
    a <- Future(r.nextInt)
    b <- Future(r.nextInt)
  } yield (a, b)
}
val result1 = Await.result(future1, 1.second)
// result1: (Int, Int) = (-1155484576, -1155484576)
val result2 = Await.result(future2, 1.second)
// result2: (Int, Int) = (-1155484576, -723955400)

I mean, I think it's because of the fact that r.nextInt is never referentially transparent, right? since identity(r.nextInt) would never be equal to identity(r.nextInt), does this mean that identity is not referentially transparent either? (or Identity monad, to have better comparisons with Future). If the expression being calculated is RT, then the Future would also be RT:

def foo(): Int = 42

val x = Future(foo())

Await.result(x, ...) == Await.result(Future(foo()), ...) // true

So as far as I can reason about the example, almost every function and Monad type should be non-RT. Or is there something special about Future? I also read this question and its answers, yet couldn't find what I was looking for.


Solution

  • You are actually right and you are touching one of the pickiest points of FP; at least in Scala.

    Technically speaking, Future on its own is RT. The important thing is that different to IO it can't wrap non-RT things into an RT description. However, you can say the same of many other types like List, or Option; so why folks don't make a fuss about it?
    Well, as with many things, the devil is in the details.

    Contrary to List or Option, Future is typically used with non-RT things; e.g. an HTTP request or a database query. Thus, the emphasis folks give in showing that Future can't guarantee RT in those situations.
    More importantly, there is only one reason to introduce Future on a codebase, concurrency (not to be confused with parallelism); otherwise, it would be the same as Try. Thus, controlling when and how those are executed is usually important.
    Which is the reason why cats recommends the use of IO for all use cases of Future

    Note: You can find a similar discussion on this cats PR and its linked discussions: https://github.com/typelevel/cats/pull/4182