1

次のようなインターフェイスを定義したシナリオに陥ることがよくあります。

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が望む関数を書くには、マクロを書く必要があると思いますか? もしそうなら、どのようにそれを行うことができますか?

4

3 に答える 3

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、最終的な値のみを変換します (自然な変換 (Peter Neyens の回答を参照) または非常に簡単に直接)。

于 2016-12-05T19:02:43.543 に答える
-1

Peter Neyen の回答 (私の質問の重要な部分に回答するため、正しいとマークしました) を拡張して、リフレクションを使用して実行時にアダプターを生成する方法の概念実証を次に示します。

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[_]] = ???

それがどのように書かれるかはよくわかりませんが...

理想的な解決策は、リフレクションを回避し、ボイラープレートを回避して、Peter Neyen のソリューションでコードを生成するコンパイラ プラグインを用意することだと思います。

于 2016-12-09T14:01:32.620 に答える