- scalaz.concurrent.Task (November, 2014)
- scalaz-stream (???)
- http4s (???)
Way back in Scala 2.9.2
- java.util.concurrent.Future ☠☠☠
- com.google.common.util.concurrent.ListenableFuture
- scala.actors.Future
- akka.dispatch.Future
- com.twitter.util.Future
- scalaz.concurrent.Promise
That begat sff4s
!scala
val factory = sff4s.impl.ActorsFuture
val f = factory future {
Thread.sleep(1000)
1
}
val g = f map { _ + 1 }
g(2000)
... which was nice for unopinionated frameworks, but never really caught on.
- Futures and Promises
- Introduced in Scala 2.10
- Backported to Scala 2.9.3
- You know it as
scala.concurrent.Future
- You know, the thing
?
returns from an actor.
- You know, the thing
- java.util.concurrent.Future ☠☠☠
- com.google.common.util.concurrent.ListenableFuture
- java.util.concurrent.CompletableFuture
scala.actors.Futurescalaz.concurrent.Promiseakka.dispatch.Future- com.twitter.util.Future
- scala.concurrent.Future
scalaz.concurrent.Promise- scalaz.concurrent.Task
- Tomorrow night is why we can't have nice things.
- For concurrent programming in Scala, we do have two rather nice things.
!scala
def isOk(url: String): Future[Boolean] = get(url)
.map { _.status == 200 } // If we got a response, was it a 200?
.recover { case e => e.printStackTrace(); false}
val sites = Seq("http://twitter.com", "http://github.com/")
val panic = Future.fold(sites.map(isOk))(false) {
(acc, isSiteOk) => acc || !isSiteOk
}
println("Panic: "+Await.result(panic, 10.seconds))
- A Future is an asynchronous computation.
- A Future tries (that is,
scala.util.Try
s) to complete that computation. - Yup. Future is a monad.
- Like Scala Future, a Scalaz Future is an asynchronous computation.
- Yup. This Future is a monad, too.
- But there's no error handling in this monad.
!scala
def isDown(url: String): Future[Boolean] = get(url)
.map { _.status != 200 } // If we got a response, was it a 200?
.recover { case e => e.printStackTrace(); false}
val sites = Seq("http://twitter.com", "http://github.com/")
val panic = Future.fold(sites.map(isDown))(false)(_ || _)
println("Panic: "+Await.result(panic, 10.seconds))
- A
Task[A]
is a thin wrapper aroundFuture[Throwable \/ A]
.scalaz.\/
is commonly pronounced as either.- Because either it worked or it didn't.
- Yup.
\/
is a monad. (scala.Either, incidentally, is not.)
- Yup.
Task
is a monad.
- This is just more of that NIH Syndrome that Scala is famous for, isn't it?
!scala
Future { TheNukes.launch() }
By Federal Government of the United States [Public domain], via Wikimedia Commons
!scala
Task { TheNukes.launch() }
Alison Rawson CC-BY-SA-2.0, via Wikimedia Commons
!scala
val task = Task { TheNukes.launch() }
headForShelter()
task.run
!scala
val f = Future { TheNukes.launch() }
Await.result(f, 1.second)
Await.result(f, 1.second)
!scala
val t = Task { TheNukes.launch() }
t.run
t.run
Fine.
!scala
val t = Task.unsafeStart { TheNukes.launch() }
t.run
t.run
!scala
val f = Future.fold(sites.map(isDown))(false)(_ || _)
val t = Task.reduceUnordered[Boolean, Boolean](sites.map(isDown))
f
will always contain the same value when completed.t
will rerun the monitoring and produce a current value on each run.
andThen
collect
filter
flatMap
foreach
onFailure
onSuccess
recover
transform
withFilter
[This slide intentionally left blank]
-
Future makes you opt out of thread hopping by explicitly setting an executor that stays on the same thread.
- It's easy to saturate your thread pool.
- It's also easy to waste time submitting trivial work to it.
- For optimal performance, swap custom execution contexts into the hotspots.
-
Task makes you opt into thread hopping.
- It's easy to accidentally only use one core in your complicated
- But it'll work really efficiently on that one core.
- For optimal performance, salt to taste with Task.apply and Task.fork
-
Bottom line: both ultimately offer the same control, promote a different kind of naive mistake.
- Twitter's Future is cancellable.
- Scala's Future is not cancellable.
- Scalaz's Task is cancellable.
!scala
val neverMind = new AtomicBoolean(false)
Task {
Thread.sleep(500)
neverMind.set(true)
}.runAsync {
case -\/(t) => t.printStackTrace()
case \/-(()) => println("Cancelled")
}
val t = Task.delay {
Thread.sleep(1000)
TheNukes.launch()
}
t.runAsyncInterruptibly({
case -\/(t) => t.printStackTrace()
case \/-(()) => println("Completed")
}, neverMind)
!scala
val neverMind = new AtomicBoolean(false)
Task {
Thread.sleep(500)
neverMind.set(true)
}.runAsync {
case -\/(t) => t.printStackTrace()
case \/-(()) => println("Cancelled")
}
val t = Task.delay {
Thread.sleep(1000)
TheNukes.launch()
}
t.runAsyncInterruptibly({
case -\/(t) => t.printStackTrace()
case \/-(()) => println("Completed")
}, neverMind)
!scala
val p = Promise[A]()
task.runAsync {
case \/-(a) => p.success(a)
case -\/(t) => p.failure(t)
}
p.future
!scala
Task.async { f =>
future.onComplete {
case Success(a) => f(right(a))
case Failure(t) => f(left(t))
}
}
- @rossabaker
- Co-organizer, IndyScala
- Principal Cloud Engineer, CrowdStrike
- We're hiring