最近会社で流行っているやつ。
Scala では Higher-Kind 型と Natural Transformation を利用して、
処理の実行モデル(同期、非同期)を抽象化することが可能になるので簡単な実装例を紹介する。
Higher-Kind 型を使う
インターフェイスを定義
インターフェイスの定義に Higher-Kind 型を利用する。
trait HogeRepository[F[_]] {
def get: F[String]
}
実装する
具体的な実行モデルを指定する。
import scala.util.Try
class HogeRepositoryImpl extends HogeRepository[Try] {
override def get: Try[String] = Try("Hoge")
}
具体的にする必要がなければ汎用なままでも良いが、必要に応じて Monad や Applicative といった制約をつけてやる。
import cats.Monad
class HogeRepositoryImpl2[F[_]: Monad] extends HogeRepository[F] {
override def get: F[String] = Monad[F].pure("Hoge")
}
利用する
利用側で具体的な型を確定する。
import scala.util.Try
object Main {
val repository: HogeRepository[Try] = new HogeRepositoryImpl()
def main(args: Array[String]): Unit = {
println(repository.get) // Success(Hoge)
}
}
このあたりは DI でやることが多い。
Natural Transformation を使う
HogeRepository[Try]
を HogeRepository[Future]
とするのにいちいち実装を書き換えたくない。そんなときは Natural Transformation を使う。
F[_]
を G[_]
に変換する仕組みを提供してくれる。
インターフェイスに mapK を実装
import cats.~>
trait HogeRepository[F[_]] {
self =>
def get: F[String]
def mapK[G[_]](nat: F ~> G): HogeRepository[G] = new HogeRepository[G] {
def get: G[String] = nat(self.get)
}
}
この F ~ G
のところが Natural Transformation。
Try ~> Future を実装する
Try ~> Future
を実装してそれを mapK
にわたすことで HogeRepository[Try]
から HogeRepository[Future]
を作ることができる。
object Main {
val tryToFuture: Try ~> Future = new (Try ~> Future) {
override def apply[A](fa: Try[A]): Future[A] = Future.fromTry(fa)
}
val repository: HogeRepository[Future] = new HogeRepositoryImpl().mapK(tryToFuture)
def main(args: Array[String]): Unit = {
println(repository.get) // Future(Success(Hoge))
}
}