我经常发现自己处于这样一个场景,我已经定义了这样的界面:
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
函数我需要写一个宏吗?如果是这样的话怎么可能呢?
答案 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的解决方案中生成代码,避免反射并避免使用样板。