OptionT

OptionT[F[_], A is a light wrapper on an F[Option[A]]. Speaking technically, it is a monad transformer for Option, but you don't need to know what that means for it to be useful. OptionT can be more convenient to work with than using F[Option[A]] directly.

Reduce map boilerplate

Consider the following scenario:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val customGreeting: Future[Option[String]] = Future.successful(Some("welcome back, Lola"))

We want to try out various forms of our greetings.

val excitedGreeting: Future[Option[String]] = customGreeting.map(_.map(_ + "!"))

val hasWelcome: Future[Option[String]] = customGreeting.map(_.filter(_.contains("welcome")))

val noWelcome: Future[Option[String]] = customGreeting.map(_.filterNot(_.contains("welcome")))

val withFallback: Future[String] = customGreeting.map(_.getOrElse("hello, there!"))

As you can see, the implementations of all of these variations are very similar. We want to call the Option operation (map, filter, filterNot, getOrElse), but since our Option is wrapped in a Future, we first need to map over the Future.

OptionT can help remove some of this boilerplate. It exposes methods that look like those on Option, but it handles the outer map call on the Future so we don't have to:

import cats.data.OptionT
import cats.std.future._

val customGreetingT: OptionT[Future, String] = OptionT(customGreeting)

val excitedGreeting: OptionT[Future, String] = customGreetingT.map(_ + "!")

val withWelcome: OptionT[Future, String] = customGreetingT.filter(_.contains("welcome"))

val noWelcome: OptionT[Future, String] = customGreetingT.filterNot(_.contains("welcome"))

val withFallback: Future[String] = customGreetingT.getOrElse("hello, there!")

Beyond map

Sometimes the operation you want to perform on an Option[Future[String]] might not be as simple as just wrapping the Option method in a Future.map call. For example, what if we want to greet the customer with their custom greeting if it exists but otherwise fall back to a default Future[String] greeting? Without OptionT, this implementation might look like:

val defaultGreeting: Future[String] = Future.successful("hello, there")

val greeting: Future[String] = customGreeting.flatMap(custom =>
  custom.map(Future.successful).getOrElse(defaultGreeting))

We can't quite turn to the getOrElse method on OptionT, because it takes a default value of type A instead of Future[A]. However, the getOrElseF method is exactly what we want:

val greeting: Future[String] = customGreetingT.getOrElseF(defaultGreeting)

Getting to the underlying instance

If you want to get the F[Option[A] value (in this case Future[Option[String]] out of an OptionT instance, you can simply call value:

val customGreeting: Future[Option[String]] = customGreetingT.value
useful links