Functional Programming in Scala: A Motivating Example 2
Now we will try to write and test asynchronous code with Futures.
Let’s imagine that we need to write some asynchronous processor.
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
class FutureProcessor {
def apply(value: Future[Int]): Future[Int] = value.map(_ + 1)
}
It is simple to write and read this code. But testing requires to use org.scalatest.concurrent.ScalaFutures.whenReady
and actually to test both your code and Future
.
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{FunSuite, MustMatchers}
import scala.concurrent.Future
class FutureProcessorSuite extends FunSuite with MustMatchers with ScalaFutures {
test("FutureProcessor") {
whenReady(new FutureProcessor().apply(Future.successful(1))) {
_ mustBe 2
}
}
}
If we start to think in terms of functional programming. It’s obvious that Future[Int]
is meaningful value: Int
inside some context that could be abstract.
import cats.Functor
class ContextProcessor {
def apply[F[_]: Functor](value: F[Int]): F[Int] = Functor[F].map(value)(_ + 1)
}
We can use it same conventional way.
import cats.instances.all._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
val f = new ContextProcessor().apply(Future.successful(1))
// f: scala.concurrent.Future[Int] = Future(<not completed>)
Await.result(f, 1.second)
// res0: Int = 2
Now we can test only our code, not Future
, and also we will not need context specific org.scalatest.concurrent.ScalaFutures.whenReady
.
import org.scalatest.{FunSuite, MustMatchers}
import cats.Id
class ContextProcessorSuite extends FunSuite with MustMatchers {
test("ContextProcessor") {
val one: Id[Int] = 1
new ContextProcessor().apply(one) mustBe 2
}
}