2016-12-05 9 views
1

その特定の実装に対して最も理にかなっています。生成アダプタ

object FooImpl1 extends FooInterface[Option] { ... } 
object FooImpl2 extends FooInterface[Future] { ... } 
object FooImpl3 extends FooInterface[({type X[Y] = ReaderT[Future, Database, Y]})#X] { ... } 

すべての実装は完全に有効です。すべての結果は、特定の高級タイプでラップされた結果を返します。

私はその後、多くの場合のは、私はコンテキストとして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アダプターの実装を既存の実装から、アダプテーションメカニズムで1回だけ指定することです。

私はこのような何かを書くことができるようにしたいと思います:

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機能を書いていると思います。私はマクロを書く必要がありますか?もしそうなら、どうすればいいのでしょうか?

答えて

1

あなたが探しているのは、XからY(あなたがX[?] => Y[?]と呼んだもの)からの自然な変換です。 Catsでは、FunctionK(一般的なタイプのエイリアス~>)と呼ばれています。

あなたはとしてOptionFuture間の自然な変換を定義することができます。

:あなたの generateAdapter関数は、その後のようになります。

val opt2fut = λ[FunctionK[Option, Future]]{ 
    case Some(obj) => Future.succesful(obj) 
    case None  => Future.failed(new Exception("none")) // t ?? 
} 

:として、これは、より簡潔に書くことができkind projector compiler plugin

import cats.arrow.FunctionK 
import scala.concurrent.Future 

val option2future = new FunctionK[Option, Future] { 
    def apply[A](opt: Option[A]): Future[A] = opt match { 
    case Some(obj) => Future.succesful(obj) 
    case None  => Future.failed(new Exception("none")) // t ?? 
    } 
} 

import cats.~> 

def generateAdapter[X[_], Y[_]](implx: FooInterface[X])(f: X ~> Y): FooInterface[Y] = 
    new FooInterface[Y] { 
    def barA: Y[Int] = f(implx.barA) 
    def barB: Y[Int] = f(implx.barB) 
    def barC: Y[Int] = f(implx.barC) 
    } 
val fooFuture = generateAdapter(FooImpl1)(opt2fut) 

関連のない、あなたはあなたが今直面しているものと同様の問題を解決するために使用されfree monad、について何かを読むために興味があるかもしれない:その後、FooInterface[Future]]として作成することができるはずです。

+0

本当に助かりました。何が起きているかを正式に指定します。特定のインタフェースの実装が多種多様であるシナリオでは、generateAdapter fnは多くの定型文の作成を節約します。 – sungiant

+0

ここには2つの質問がありますが、そのうちの最初の方がうまく答えていますが、これから続けるためにはgenerateAdaptor関数を定義するときに書かれたコードは事実上規範的ですが、これは特に多くの関数この例のものよりもこれを直接書くのではなく、代わりに実装を生成する方法はありますか?多分リフレクション... – sungiant

1

コードをできるだけ長く保存してください。代わりに

val result: Future[Int] = for { 
    x <- foo.barA() 
    y <- foo.barB() 
    z <- foo.barC() 
    w <- fn() 
} yield x + y + z + w 

書き込み

import scalaz.Monad 
import scalaz.syntax.monad._ 
// or 
import cats.Monad 
import cats.syntax.all._ 

def result[M[_]: Monad](foo: FooInterface[M], fn:() => M[Int]): M[Int] = for { 
    x <- foo.barA() 
    y <- foo.barB() 
    z <- foo.barC() 
    w <- fn() 
} yield x + y + z + w 

この方法で、あなたは完全にFooInterfaceためのアダプタを書くだけで自然形質転換を経由して(最終値を変換避ける(ピーターNeyens'の答えを参照)、またはも非常に簡単に直接)。 (私はそれは私の質問の重要な部分を答えとして正しいマークした)ピーターNeyenの答えに拡大

-1

は、ここにリフレクションを使用して、実行時にアダプターを生成する方法のためのコンセプトの証明です:

def generateAdapterR[X[_], Y[_]](implx: FooInterface[X])(implicit 
    f: X ~> Y): FooInterface[Y] = { 
    import java.lang.reflect.{InvocationHandler, Method, Proxy} 
    object ProxyInvocationHandler extends InvocationHandler { 
    def invoke (
     proxy: scala.AnyRef, 
     method: Method, 
     args: Array[AnyRef]): AnyRef = { 
     val fn = implx.getClass.getMethod (
     method.getName, 
     method.getParameterTypes: _*) 
     val x = fn.invoke (implx, args: _*) 
     val fx = f.getClass.getMethods()(0) 
     fx.invoke (f, x) 
    } 
    } 
    Proxy.newProxyInstance(
    classOf[FooInterface[Y]].getClassLoader, 
    Array(classOf[FooInterface[Y]]), 
    ProxyInvocationHandler 
).asInstanceOf[FooInterface[Y]] 
} 

理想的には、この関数をT[_]に入力することも可能です。Tはインタフェースのタイプなので、実行時に上位の種類のインタフェース用のアダプタを生成するために使用できます。

ような何か:それはしかし、それを記述する方法であるかどう

def genericGenerateAdapterR[T[_], X[_], Y[_]](implx: T[X[_]])(implicit 
    f: X ~> Y): T[Y[_]] = ??? 

本当にわからない...

私は理想的なソリューションは、内のコードを生成するコンパイラプラグインを持っているだろうと思いますピーター・ネイエンの解決策、反省を避け、定型句を避ける。

+0

上記のリフレクションコードを書く、より良い、より安全な方法を誰かが知っているなら、具体的にはランタイムの高いkinded型を扱うときには、それを適切に扱う方法を知りたいと思います。 – sungiant

関連する問題