Pros and Cons of different solutions:
import scala.language.higherKinds
trait AsyncDemo[F[_]] {
type ArtistAndRelated = (String, List[String])
def findArtistAndRelated(query: String): F[ArtistAndRelated]
}
def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T]
def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S]
def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S]
Future(1).map(_ + 1)
trait FutureDemo extends AsyncDemo[Future] {
self: SpotifyApi =>
override def findArtistAndRelated(query: String): Future[ArtistAndRelated] =
for {
result <- Future { searchArtist(query) }
artist <- Future { parseArtistId(result) }
artists <- Future { relatedArtists(artist.id) }
names <- Future { parseArtistNames(artists) }
} yield (artist.name, names)
}
def apply[A](a: => A): Future[A]
def map[B](f: A => B): Future[B]
def flatMap[B](f: A => Future[B]): Future[B]
class ConstFuture[A](result: Try[A]) extends Future[A] {
def respond(k: Try[A] => Unit): Future[A] = {
val saved = Local.save()
Scheduler.submit(new Runnable {
def run(): Unit = {
val current = Local.save()
Local.restore(saved)
try k(result)
catch Monitor.catcher
finally Local.restore(current)
}
})
this
}
}
trait FutureDemo extends AsyncDemo[Future] {
self: SpotifyApi =>
override def findArtistAndRelated(query: String): Future[ArtistAndRelated] =
for {
result <- Future { searchArtist(query) }
artist <- Future { parseArtistId(result) }
artists <- Future { relatedArtists(artist.id) }
names <- Future { parseArtistNames(artists) }
} yield (artist.name, names)
}
class Task[+A](val get: Future[Throwable \/ A])
def map[B](f: A => B): Task[B]
def flatMap[B](f: A => Task[B]): Task[B]
def apply[A](a: => A)(implicit pool: ExecutorService): Task[A]
def unsafeStart[A](a: => A)(implicit pool: ExecutorService): Task[A]
import scalaz.concurrent.Task
trait ScalazTaskDemo extends AsyncDemo[Task] {
self: SpotifyApi =>
override def findArtistAndRelated(query: String): Task[ArtistAndRelated] =
for {
result <- Task.delay { searchArtist(query) }
artist <- Task.delay { parseArtistId(result) }
artists <- Task.delay { relatedArtists(artist.id) }
names <- Task.delay { parseArtistNames(artists) }
} yield (artist.name, names)
}
final class Task[+A](private[fs2] val get: Future[Attempt[A]])
def apply[A](a: => A)(implicit S: Strategy): Task[A]
def map[B](f: A => B): Task[B]
def flatMap[B](f: A => Task[B]): Task[B]
private[fs2] object Trampoline {
private final case class Return[A](a: A) extends Trampoline[A]
private final case class Suspend[A](resume: () => A) extends Trampoline[A]
private final case class FlatMap[A,B](sub: Trampoline[A], k: A => Trampoline[B])
extends Trampoline[B]
def delay[A](a: => A): Trampoline[A] = Suspend(() => a)
def done[A](a: A): Trampoline[A] = Return(a)
def suspend[A](a: => Trampoline[A]) =
Suspend(() => ()).flatMap { _ => a }
@annotation.tailrec
def run[A](t: Trampoline[A]): A = t match {
case Return(a) => a
case Suspend(r) => r()
case FlatMap(x, f) => x match {
case Return(a) => run(f(a))
case Suspend(r) => run(f(r()))
case FlatMap(y, g) => run(y flatMap (a => g(a) flatMap f))
}
}
}
def flatMap[B](f: A => Future[B]): Future[B] = this match {
case Now(a) => Suspend(() => f(a))
case Suspend(thunk) => BindSuspend(thunk, f)
case Async(listen) => BindAsync(listen, f)
case BindSuspend(thunk, g) =>
Suspend(() => BindSuspend(thunk, g andThen (_ flatMap f)))
case BindAsync(listen, g) =>
Suspend(() => BindAsync(listen, g andThen (_ flatMap f)))
}
def map[B](f: A => B): Future[B] =
flatMap(f andThen (b => Future.now(b)))
import fs2.Task
trait ScalazTaskDemo extends AsyncDemo[Task] {
self: SpotifyApi =>
override def findArtistAndRelated(query: String): Task[ArtistAndRelated] =
for {
result <- Task.delay { searchArtist(query) }
artist <- Task.delay { parseArtistId(result) }
artists <- Task.delay { relatedArtists(artist.id) }
names <- Task.delay { parseArtistNames(artists) }
} yield (artist.name, names)
}
sealed abstract class Task[+A] extends Serializable
def apply[A](f: => A): Task[A]
def map[B](f: A => B): Task[B]
def flatMap[B](f: A => Task[B]): Task[B]
def runAsync(cb: Callback[A])(implicit s: Scheduler): Cancelable = {
val context = Context(s)
val frameStart = TaskRunLoop.frameStart(s.executionModel)
TaskRunLoop.startWithCallback(self, context, Callback.safe(cb),
null, null, frameStart)
context.connection
}
import monix.eval._
trait MonixTaskDemo extends AsyncDemo[Task] {
self: SpotifyApi =>
override def findArtistAndRelated(query: String): Task[ArtistAndRelated] =
for {
result <- Task.eval { searchArtist(query) }
artist <- Task.eval { parseArtistId(result) }
artists <- Task.eval { relatedArtists(artist.id) }
names <- Task.eval { parseArtistNames(artists) }
} yield (artist.name, names)
}
Benchmark tool: sbt-jmh
Graphics: jmh-charts
> sbt "jmh:run -rf json -i 20 -wi 20 -f1 -t1 .*AsyncBenchmark.*"
> sbt "jmh:run -rf json -i 10 -wi 10 -f1 -t1 .*AsyncBenchmark.*"
> sbt "jmh:run -rf json -i 20 -wi 20 -f1 -t1 .*TaskGatherBenchmark.*"
package fs2
def fromFuture[A](fut: => urrent.Future[A]): Task[A]
package monix.eval def fromFuture[A](f: Future[A]): Task[A]