为更高通道的接口生成适配器

时间:2016-12-05 12:14:51

标签: scala scalaz scala-macros scala-cats

我经常发现自己处于这样一个场景,我已经定义了这样的界面:

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] { ... }

所有实现都是完全有效的,所有实现都返回包含在特定高级类型中的结果。

然后我经常写一些业务逻辑,让我们说在我使用的逻辑块中使用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函数我需要写一个宏吗?如果是这样的话怎么可能呢?

2 个答案:

答案 0 :(得分: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的回答)或者很容易直接转换)。

答案 1 :(得分:-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的解决方案中生成代码,避免反射并避免使用样板。