次のようなインターフェイスを定義したシナリオに陥ることがよくあります。
trait FooInterface [T[_]] {
def barA (): T[Int]
def barB (): T[Int]
def barC (): T[Int]
}
次に、特定の実装に最も適した Higher Kinded Type で型指定されたいくつかの異なる実装を記述します。
object FooImpl1 extends FooInterface[Option] { ... }
object FooImpl2 extends FooInterface[Future] { ... }
object FooImpl3 extends FooInterface[({type X[Y] = ReaderT[Future, Database, Y]})#X] { ... }
すべての実装は完全に有効であり、特定の Higher Kinded Type にラップされた結果を返します。
次に、ビジネス ロジックを記述することがよくあります。たとえば、使用しているロジックのブロックでFuture
コンテキストとして使用しているとします。たとえば、次のようなものを記述します。
val foo: FooInterface[Future] = ???
def fn (): Future[Int] = Future { 42 }
val result: Future[Int] = for {
x <- foo.barA ()
y <- foo.barB ()
z <- foo.barC ()
w <- fn ()
} yield x + y + z + w
上記のコードは非常にうまく機能しますFooImpl2
が、他の実装は直接スロットインしません。このシナリオでは、私はいつも単純なアダプターを書くことになります:
object FooImpl1Adapter extends FooInterface[Future] {
val t = new Exception ("Foo impl 1 failed.")
def barA (): Future[Int] = FooImpl1.barA () match {
case Some (num) => Future.successful (num)
case None => Future.failed (t)
}
def barB (): Future[Int] = FooImpl1.barB () match {
case Some (num) => Future.successful (num)
case None => Future.failed (t)
}
def barC (): Future[Int] = FooImpl1.barC () match {
case Some (num) => Future.successful (num)
case None => Future.failed (t)
}
}
case class FooImpl3Adapter (db: Database) extends FooInterface[Future] {
def barA (): Future[Int] = FooImpl3.barA ().run (db)
def barB (): Future[Int] = FooImpl3.barB ().run (db)
def barC (): Future[Int] = FooImpl3.barC ().run (db)
}
アダプタの記述は問題ありませんが、多くの定型句が必要です。特に、多くの機能を持つインターフェイスの場合はそうです。さらに、各メソッドは、各メソッドに対してまったく同じ適応処理を取得します。私が本当にやりたいのはlift
、既存の実装からのアダプターの実装です。適応メカニズムで一度だけ指定します。
私はこのようなものを書くことができるようになりたいと思います:
def generateAdapterFn[X[_], Y[_]] (implx: FooInterface[X])(f: X[?] => Y[?]): FooInterface[Y] = ???
だから私はそれを次のように使うことができます:
val fooImpl1Adapter: FooInterface[Future] = generateAdapterFn [?, Future] () { z => z match {
case Some (obj) => Future.successful (obj)
case None => Future.failed (t)
}}
generateAdapterFn
問題は、関数をどのように記述できるかです。
これを解決する方法、または私の問題に対する他の一般的なパターンや解決策があるかどうかはよくわかりません。私generateAdapterFn
が望む関数を書くには、マクロを書く必要があると思いますか? もしそうなら、どのようにそれを行うことができますか?