初めに
OptionTの簡単な説明…OptionT[F[_], A]は、F[Option[A]]の軽量ラッパーです。 OptionをFutureなどの型内に配置するような処理をする際、OptionTを使うことによりコードをスッキリまとめやすくなります。
Scala with CatsのOptionTについて公式ドキュメントを自分なりに翻訳して説明していきます!!
※ところどころ、分かりやすいように文を付け加えてます!
まえがき
OptionT[F[_], A]は、F[Option[A]]の軽量ラッパーです。 技術的に言えば、これはOptionのモナド変換子ですが、それが役立つために何を意味するのかを知る必要は無いでしょう。 OptionTは、F[Option[A]]を直接使用するよりも便利です。
mapボイラープレートを減らす
ボイラープレートとは…仕様上省略不能で、かつほとんど変更を加えることなく多くの場所に組み込む必要があるソースコードのこと。[Wikipediaより]
次のシナリオを検討しましょう。
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val customGreeting: Future[Option[String]] = Future.successful(Some("welcome back, Lola"))
いろいろな形のあいさつをやってみましょう。
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!"))
ご覧のとおり、これら全てのバリエーションの実装は非常に似ています。Optionの操作(map、filter、filterNot、getOrElse)を呼び出したいのですが、OptionはFutureにラップされているため、最初にFutureにmapする必要があります。
OptionTはこの定型文の一部を削除するのに役立ちます。OptionTはOptionのメソッドのように見えるメソッドを公開していますが、Futureの外部のmapを呼び出すため、これを行う必要はありません。
import cats.data.OptionT
import cats.implicits._
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!")
Option[A]および/またはF[A]からOptionT[F, A]へ
Option[A]および/またはF[A]があり、それらをOptionT[F, A]にリフトしたい場合があるでしょう。このために、OptionTは2つの便利なメソッド、fromOptionとliftFを公開しています。例えば、以下のように使用します。
val greetingFO: Future[Option[String]] = Future.successful(Some("Hello"))
val firstnameF: Future[String] = Future.successful("Jane")
val lastnameO: Option[String] = Some("Doe")
val ot: OptionT[Future, String] = for {
g <- OptionT(greetingFO)
f <- OptionT.liftF(firstnameF)
l <- OptionT.fromOption[Future](lastnameO)
} yield s"$g $f $l"
val result: Future[Option[String]] = ot.value // Future(Some("Hello Jane Doe"))
AからOptionT[F,A]へ
Aのみがあり、FのApplicativeインスタンスがあると仮定して、それをOptionT[F,A]にリフトしたい場合は、pureと同意であるsomeを使用できます。OptionT[F,A]を作成するためのnoneメソッドもあります。これにより、OptionラップされたAの型は実際はNoneです。
val greet: OptionT[Future,String] = OptionT.pure("Hola!")
val greetAlt: OptionT[Future,String] = OptionT.some("Hi!")
val failedGreet: OptionT[Future,String] = OptionT.none
mapを超える
Future[Option[String]]に対して実行する操作は、Future.map呼び出しでOptionメソッドをラップするほど単純ではない場合があります。例えば、カスタムgreetingが存在する場合はそれを使用し、それ以外はデフォルトのFuture[String]greetingに戻りたい場合はどうでしょうか?OptionTがないと、この実装は次のようになります。
val defaultGreeting: Future[String] = Future.successful("hello, there")
val greeting: Future[String] = customGreeting.flatMap(custom =>
custom.map(Future.successful).getOrElse(defaultGreeting))
OptionTのgetOrElseメソッドは、Future[A]ではなく型Aのデフォルト値を使用するため、完全に利用することはできません。しかし、getOrElseFメソッドはまさに私たちが望むものです。
val greeting: Future[String] = customGreetingT.getOrElseF(defaultGreeting)
基になるインスタンスへのアクセス
OptionTインスタンスからF[Option[A]]値(この場合はFuture[Option[String]])を取得する場合は、valueを呼び出すだけです。
val customGreeting: Future[Option[String]] = customGreetingT.value
以上で公式ドキュメントよりScala with CatsのOptionTについて説明終わりです!
ありがとうございました!!🙇♂️