Future vs Task - Referential Transparency


I was going through this post on reddit on futures. In the comments there is an argument that goes something like this

  val x = Future(r.nextInt)
 (x, x)

should be the same as

(Future(r.nextInt), Future(r.nextInt))

to make it referentially transparent. But they are not the same. And hence futures have been deemed as unusable. But isn’t the referential transparency lost because of r.nextInt and not because of future itself?


Code is referentially transparent if you can replace the definition with its body at any point (in other words, you can replace the reference (in the example x) with the value (in the example Future(r.nextInt)).

In the example, val x = Future.apply(e) would be referentially transparent if you can replace x with Future.apply(e) for all e

You can’t, you note so yourself:

But they are not the same.

If those are not the same, that’s the definition of them not being referentially transparent.

I don’t see the post claiming that Future is unsuable in general though, just that the writer of the tweet finds it unusable after being accustomed to a referentially transparent abstraction:

After years of using Scalaz Task, Future is now totally unusable.

implies that before getting accustomed to using Task, Future wasn’t unusable to them, and it doesn’t really say anything about other people that may find Future a totally usable abstraction.


Yes, in the example, the usage of r.nextInt is s bigger problem to referential transparency than Future.

If some one wants to be really pure FP and strictly only use immutable objects, then that excludes Future. But it also excludes any kind of I/O or any non-trivial asynchronous stuff. So usually, people accept a minimal amount of mutability, where you build immutable recipes of what you want to do and delay the actual execution of these recipes as long as possible. Futures are one way to do that.

1 Like

IO in the large can’t be functional. In many ways that is the definition of IO.

Futures are somewhere in the middle. Their semantics can’t be functional. Their origins go back to problems like pipelining data from tape to big disks to fast disks to global memory to local memory in high speed mathematic codes on fast/super computers. Back in the 50s-80s it required a more disciplined approach. After that, compute resources were cheap enough for Futures to be good enough. TCP I/O has made it more useful today. Principled use of UDP and non-IP networking protocols can solve much of the problem for many problems. Futures being good enough to avoid much work.

At the end legitimate use of futures is tightly bound to IO. If they are being used for non-IO and functional programming is desired, futures are the wrong approach to take.


Being referentially transparent has a definition. This use of Future is not referentially transparent. There is no “yes but”'ing there, it’s just not. Saying something is not referentially transparent is not an insult, it’s an observation about a fact.

And while you can’t perform IO in a referentially transparent way, you can work with IO in a referentially transparent way that is useful and gets the work done.

Someone may find working with non-referentially transparent abstractions unworkable. When they have an FP code base, that sounds plausible.

1 Like