我经常发现自己处于这样一个场景中,我定义了这样的接口:
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
我想要的函数,我需要编写一个宏?如果是这样,那该怎么做?