Is Future in Scala a monad?
Asked Answered
I

4

58

Why and how specifically is a Scala Future not a Monad; and would someone please compare it to something that is a Monad, like an Option?

The reason I'm asking is Daniel Westheide's The Neophyte's Guide to Scala Part 8: Welcome to the Future where I asked whether or not a Scala Future was a Monad, and the author responded that it wasn't, which threw off base. I came here to ask for a clarification.

Irresolute answered 13/12, 2014 at 2:9 Comment(6)
does it have flatMap? yes, can it be used in for-comprehensions? yes, then it's a monad.Habile
@ElectricCoffee no.Oleary
@PabloFernandez Scala's flatMap is Haskell's >>=, and Scala's for-comprehensions are equivalent to Haskell's do notation. If it looks like a duck and quacks like a duck...Habile
@ElectricCoffee sure, but monads are not only bind (or >>=)Oleary
@PabloFernandez Monads implement two and only two things. Bind and Identity, everything else are nice-to-have things. If a future complies with the monadic laws and implements those two things, then it's a monadHabile
@ElectricCoffee It is not a monad because it just implements two functions with specific names. There are three monadic laws that have to be satisfied, see accepted answer.Chee
W
102

A summary first

Futures can be considered monads if you never construct them with effectful blocks (pure, in-memory computation), or if any effects generated are not considered as part of semantic equivalence (like logging messages). However, this isn't how most people use them in practice. For most people using effectful Futures (which includes most uses of Akka and various web frameworks), they simply aren't monads.

Fortunately, a library called Scalaz provides an abstraction called Task that doesn't have any problems with or without effects.

A monad definition

Let's review briefly what a monad is. A monad must be able to define at least these two functions:

def unit[A](block: => A)
    : Future[A]

def bind[A, B](fa: Future[A])(f: A => Future[B])
    : Future[B]

And these functions must statisfy three laws:

  • Left identity: bind(unit(a))(f) ≡ f(a)
  • Right identity: bind(m) { unit(_) } ≡ m
  • Associativity: bind(bind(m)(f))(g) ≡ bind(m) { x => bind(f(x))(g) }

These laws must hold for all possible values by definition of a monad. If they don't, then we simply don't have a monad.

There are other ways to define a monad that are more or less the same. This one is popular.

Effects lead to non-values

Almost every usage of Future that I've seen uses it for asychronous effects, input/output with an external system like a web service or a database. When we do this, a Future isn't even a value, and mathematical terms like monads only describe values.

This problem arises because Futures execute immediately upon data contruction. This messes up the ability to substitute expressions with their evaluated values (which some people call "referential transparency"). This is one way to understand why Scala's Futures are inadequate for functional programming with effects.

Here's an illustration of the problem. If we have two effects:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits._


def twoEffects =
  ( Future { println("hello") },
    Future { println("hello") } )

we will have two printings of "hello" upon calling twoEffects:

scala> twoEffects
hello
hello

scala> twoEffects
hello
hello

But if Futures were values, we should be able to factor out the common expression:

lazy val anEffect = Future { println("hello") }

def twoEffects = (anEffect, anEffect)

But this doesn't give us the same effect:

scala> twoEffects
hello

scala> twoEffects

The first call to twoEffects runs the effect and caches the result, so the effect isn't run the second time we call twoEffects.

With Futures, we end up having to think about the evaluation policy of the language. For instance, in the example above, the fact I use a lazy value rather than a strict one makes a difference in the operational semantics. This is exactly the kind of twisted reasoning functional programming is designed to avoid -- and it does it by programming with values.

Without substitution, laws break

In the presense of effects, monad laws break. Superficially, the laws appear to hold for simple cases, but the moment we begin to substitute expressions with their evaluated values, we end up with the same problems we illustrated above. We simply can't talk about mathematical concepts like monads when we don't have values in the first place.

To put it bluntly, if you use effects with your Futures, saying they're monads is not even wrong because they aren't even values.

To see how monad laws break, just factor out your effectful Future:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits._


def unit[A]
    (block: => A)
    : Future[A] =
  Future(block)

def bind[A, B]
    (fa: Future[A])
    (f: A => Future[B])
    : Future[B] =
  fa flatMap f

lazy val effect = Future { println("hello") }

Again, it will only run one time, but you need it to run twice -- once for the right-hand side of the law, and another for the left. I'll illustrate the problem for the right identity law:

scala> effect  // RHS has effect
hello

scala> bind(effect) { unit(_) }  // LHS doesn't

The implicit ExecutionContext

Without putting an ExecutionContext in implicit scope, we can't define either unit or bind in our monad. This is because the Scala API for Futures has these signature:

object Future {
  // what we need to define unit
  def apply[T]
      (body: ⇒ T)
      (implicit executor: ExecutionContext)
      : Future[T]
}

trait Future {
   // what we need to define bind
   flatMap[S]
       (f: T ⇒ Future[S])
       (implicit executor: ExecutionContext)
       : Future[S]
}

As a "convenience" to the user, the standard library encourages users to define an execution context in implicit scope, but I think this is a huge hole in the API that just leads to defects. One scope of the computation may have one execution context defined while another scope can have another context defined.

Perhaps you can ignore the problem if you define an instance of unit and bind that pins both operations to a single context and use this instance consistently. But this is not what people do most of the time. Most of the time, people use Futures with for-yield comprehensions that become map and flatMap calls. To make for-yield comprehensions work, an execution context must be defined at some non-global implicit scope (because for-yield doesn't provide a way to specify additional parameters to the map and flatMap calls).

To be clear, Scala lets you use lots of things with for-yield comprehensions that aren't actually monads, so don't believe that you have a monad just because it works with for-yield syntax.

A better way

There's a nice library for Scala called Scalaz that has an abstraction called scalaz.concurrent.Task. This abstraction doesn't run effects upon data construction as the standard library Future does. Furthermore, Task actually is a monad. We compose Task monadically (we can use for-yield comprehensions if we like), and no effects run while we're composing. We have our final program when we have composed a single expression evaluating to Task[Unit]. This ends up being our equivalent of a "main" function, and we can finally run it.

Here's an example illustrating how we can substitute Task expressions with their respective evaluated values:

import scalaz.concurrent.Task
import scalaz.IList
import scalaz.syntax.traverse._


def twoEffects =
  IList(
    Task delay { println("hello") },
    Task delay { println("hello") }).sequence_

We will have two printings of "hello" upon calling twoEffects:

scala> twoEffects.run
hello
hello

And if we factor out the common effect,

lazy val anEffect = Task delay { println("hello") }

def twoEffects =
  IList(anEffect, anEffect).sequence_

we get what we'd expect:

scala> twoEffects.run
hello
hello

In fact, it doesn't really matter that whether we use a lazy value or a strict value with Task; we get hello printed out twice either way.

If you want to functionally program, consider using Task everywhere you may use Futures. If an API forces Futures upon you, you can convert the Future to a Task:

import concurrent.
  { ExecutionContext, Future, Promise }
import util.Try
import scalaz.\/
import scalaz.concurrent.Task


def fromScalaDeferred[A]
    (future: => Future[A])
    (ec: ExecutionContext)
    : Task[A] =
  Task
    .delay { unsafeFromScala(future)(ec) }
    .flatMap(identity)

def unsafeToScala[A]
    (task: Task[A])
    : Future[A] = {
  val p = Promise[A]
  task.runAsync { res =>
    res.fold(p failure _, p success _)
  }
  p.future
}

private def unsafeFromScala[A]
    (future: Future[A])
    (ec: ExecutionContext)
    : Task[A] =
  Task.async(
    handlerConversion
      .andThen { future.onComplete(_)(ec) })

private def handlerConversion[A]
    : ((Throwable \/ A) => Unit)
      => Try[A]
      => Unit =
  callback =>
    { t: Try[A] => \/ fromTryCatch t.get }
      .andThen(callback)

The "unsafe" functions run the Task, exposing any internal effects as side-effects. So try not to call any of these "unsafe" functions until you've composed one giant Task for your entire program.

Windhover answered 14/12, 2014 at 6:50 Comment(24)
Your proof that futures aren't values is wrong - you have a future that performs a side effect - that same technique can show that any monad is not a value. A future in fact is a temporal value - that's exactly what it is. You even acknowledge this in your "proof" - if you know your proof is wrong - why include it?. The exact same thing for your execution context argument - it also seems incorrect.Pompous
I also don't see why the monad laws don't hold - in fact it's quite simple to prove that they do hold - again, assuming you don't explicitly perform side effects which afaik there is no way to prevent you from anyway. Also, scalaz tasks aren't monads either, they're more like comonads if you look at the definition closely. While it's possible they provide a better API your arguments about monads with Futures were incorrect.Pompous
There's a difference between an effect and a side-effect. Haskell's IO is a value, and just describes an effect. Scalaz's Task is also just an effect. Future has side-effects. This is very important to understand when doing FP.Windhover
You're argument that Scalaz's Task are like comonads is largely because of the "unsafe" run that seems similar to copoint. This is merely an encoding on the JVM, which is not a machine designed to run functional programs. We compose expressions in FP until we execute them "at the end of the world." Expressions can define effects and non-effectful calculations.Windhover
I'm not sure how Haskell is related to the question other than just being a FP language... As for the argument - the fact you can perform side effects in Scala does not make it not a monad any more than Tasks (since I could easily construct an example where those cause interfering side effects too). The fact Futures are monads is simply because they actually do abide by the monad laws (much like Tasks do with comonads).Pompous
As fora future is a temporal value since you brought up Haskell you might want to see this - or this.Pompous
With Task, you /can/ break it, and honestly, you should file a bug with Scalaz if this bothers you. With Futures it's always broken.Windhover
You're right that Future is fine for non-effecting computations, and I point that out in my writeup, but this is /not/ at all how most people use Futures, and it's very important to break up the misinformation. Furthermore, calling Task a comonad is very, very wrong. There is no Comonad instance for Task in Scalaz, even though the Comonad type class exists. There's a huge reason for this.Windhover
As for Erik Miejer's dabble into the world of side-effects, I'm kind of at a loss for words. People are citing him as an authority, and I have a huge amount of respect for his knowledge and accomplishments. I've tried to press for more of a formalism from him, but what I generally get is reference implementations. I'm not sure Erik cares. I think he's got a "when in Rome" attitude when it comes to side-effects.Windhover
Also, we're circling around the idea of whether Futures should be used for effects or not. I think all your arguments are fine in an effect-less computation (even that Future is a comonad), but this is not at all how people are using it. However, there's also the problem that the execution context breaks the signatures -- which is my other gripe. The abstraction is just very broken.Windhover
Let us continue this discussion in chat.Windhover
I don't know if it's true to say that most people use Scala Futures for effects. I use them extensively in web services for consuming APIs asynchronously.Maurili
acjay, using Scala's Future's for web services /is/ using them for effects, which means that you're susceptible to all the problems I penned up above, and none of the safety that Benjamin discusses. Effects can be either input or output. With web services, they can be both. There's so much nondeterminism and possibly state in play when you call web services, not to mention the service can go down at any moment. This is why it's so helpful to tackle the complexity of this distributed system with values (as Task does) rather than with side-effects (as Future does).Windhover
Could you be more explicit about how the laws don't hold? The forceful rhetoric and "not even wrong" is all well and good, but it would be more constructive to have concrete examples (compare e.g. SI-6284, which has a code example for why Try violates the functor laws).Stoltzfus
I got a feeling that you are mixing side effects with values, thus coming to a weird, mostly wrong, conclusion. If you provided stricter definitions of all the terms, you would probably be able to see by yourself that actually no law is broken here.Nitpicking
Hi Vlad, in this post I'm trying to make a distinction between "side effects" and "effects." Some people prefer to say "explicit effects" versus "implicit effects" (side effects). Nothing is stopping us from talking about explicit (non-side) effects as values. Many people experience this with the IO type in Haskell. We can also have effects as values with scalaz.effect.IO, scalaz.concurrent.Task, or something similar. The point of this article is that when we use standard library Futures for implicit side effects, all bets are off. They aren't even values, and can't be monadic.Windhover
What's the type of Future {println("hello");}? When the answer becomes apparent, maybe it will also become apparent that Task[A] in scalaz is actually Task[IO[A]]. There's also magic in unit (block: => A): Future[A] suddenly being a substitute for A => M[A] for some monad M. Why do you include the effect of => A as something Future must take care of? Look, Task is not treated the same way - there you demand the use of delayAbstemious
@Sassa, people use Future for (side-)effects all the time. And it leads to defects, because effects are important for program semantics. This is why I insist that we want beta-equivalence (referential transparency), even for effects. We use Task.delay as a way to wrangle side-effects into pure functions.Windhover
I am not arguing about how people use Future. I am making a point that the type isn't Future[IO[A]], that is, the side-effects are not reflected in the type. On the other hand, Task[A] really represents something closer to a composition of the two: Future[IO[A]]. And I am making a point that it is not accurate to assert unit: (block : => A): Future[A] "fails" to be a monad, because the effectful part is in => A, not in A. Same goes for Task - the effectful part is in delay, not in A.Abstemious
@SassaNF I think I see the point you're trying to make. All I can say is that you can't mix a mathematical notion with something that breaks its formalism. It's like scribbling nonsense into a valid proof. Programming languages confuse us on this point. But if we're going to say "monad" we shouldn't make arbitrary exceptions based on evaluation policy. FP by design should make evaluation policy resource-management concern, not affect semantics.Windhover
@Sukant Hajra - I think the problem that people are having with your proof is that your are conflating "equality" with "having the same side-effects." In a functional language, there are no side-effects, so that distinction is moot. But, in imperative languages, there's a world of difference between saying "expression X has the same side effects as expression Y" and "expression X results in the same value as expression Y." It is definitely different kind of Monad than the IO Monads of Haskell, but whether or not its a monad depends on your interpretation of equivalency in the monadic laws.Cantabrigian
@Cantabrigian if you care about the semantics of side-effects and want to say "monad" then then my notion of equality should apply (which is why many people have no problem with my post, and endorse it). You're right, there are a class of effects people don't care as much about. For instance, consider debug logging. If a log fires twice instead of once, people may not consider the specification broken. But these examples, I think, are not the norm -- especially in Scala!Windhover
@Sukant Hajra - Except, when people say "Future is a Monad", they're not talking about side-effects. Its kinda dubious that you even should consider side-effects, because Monad is a type, which describes values (or objects if you prefer), and values don't have side-effects. Expressions have side-effects, values don't. This is where your interpretation of the monadic laws and those those that say Future is a Monad differ: you're saying expression X must be equivalent to expression Y, and they're saying value of expression X is equivalent to the value of expression Y.Cantabrigian
In this sense, I believe the monadic laws hold for Futures, making them monads in the same way that lists and maybe are monads: they're values that can be manipulated in a monadic way. Its tempting for people to think that Futures are like the IO Monads from Haskell (which are the most well known ways to use monads) or Scala'z Task, but as you've pointed out, these have very different semantics. But, these semantics aren't strictly part of the monadic laws, as far as I can tell.Cantabrigian
T
9

I believe a Future is a Monad, with the following definitions:

def unit[A](x: A): Future[A] = Future.successful(x)

def bind[A, B](m: Future[A])(fun: A => Future[B]): Future[B] = fut.flatMap(fun)

Considering the three laws:

  1. Left identity:

    Future.successful(a).flatMap(f) is equivalent to f(a). Check.

  2. Right identity:

    m.flatMap(Future.successful _) is equivalent to m (minus some possible performance implications). Check.

  3. Associativity m.flatMap(f).flatMap(g) is equivalent to m.flatMap(x => f(x).flatMap(g)). Check.

Rebuttal to "Without substitution, laws break"

The meaning of equivalent in the monad laws, as I understand it, is you could replace one side of the expression with the other side in your code without changing the behavior of the program. Assuming you always use the same execution context, I think that is the case. In the example @sukant gave, it would have had the same issue if it had used Option instead of Future. I don't think the fact that the futures are evaluated eagerly is relevant.

Tecu answered 13/4, 2018 at 23:26 Comment(0)
B
2

I have to teach futures in a class, so I was looking at this and came back really puzzled. In particular about this statement and the example that comes with it.

To see how monad laws break, just factor out your effectful Future:

I stared for a long time at the code. I could reproduce it in the repl, but could not figure out why the behavior would be different. The reason given (futures start immediately) seemed wrong, I could not see how that would make a difference. Then it clicked. It's because of the JVM's quirk that a program shuts down at the end of main, no matter whether there are still tasks to execute or not. It's simply that in one of the examples, the future has already finished when the repl line is finished and in the other it has not. If you do a thread sleep after the code, both examples print.

In other word it's a race condition between a program and the JVM's exit after main.

Bio answered 8/12, 2023 at 16:22 Comment(0)
L
0

As the other commenters have suggested, you are mistaken. Scala's Future type has the monadic properties:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits._

def unit[A](block: => A): Future[A] = Future(block)
def bind[A, B](fut: Future[A])(fun: A => Future[B]): Future[B] = fut.flatMap(fun)

This is why you can use for-comprehension syntax with futures in Scala.

Lamphere answered 13/12, 2014 at 10:39 Comment(1)
Hi 0__, I believe @Sukan't answer to be correct; it is not sufficient to have monadic properties but they also have to satisfy the 3 laws.Irresolute

© 2022 - 2024 — McMap. All rights reserved.