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

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

然后我经常来写一些业务逻辑,假设在我正在使用的逻辑块中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

您正在寻找的是从XY(您所说的X[?] => Y[?])的自然转换。在 Cats 中称为 a FunctionK(具有流行的类型别名~>)。

Option您可以在和之间定义一个自然转换Future

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 ??
  }
}

使用kind 投影仪编译器插件,可以更简洁地编写为:

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

您的generateAdapter函数可能如下所示:

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)
  }

然后,您应该能够创建一个FooInterface[Future]]as :

val fooFuture = generateAdapter(FooImpl1)(opt2fut)

无关,您可能有兴趣阅读有关free monad的内容,它用于解决与您现在面临的类似问题。

于 2016-12-05T13:42:34.953 回答
-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 回答