我目前正在使用scala进行编码,我认为自己是新手。 我有3个不受我控制的课程,这意味着我无法改变它们。
class P
class A {
def m(p: P): A = { println("A.m"); this }
}
class B {
def m(p: P): B = { println("B.m"); this }
}
这是一个简化的例子,实际代码更复杂,类A,B有许多其他类似的方法。
我需要为类A,B的实例调用方法m
显而易见的解决方案是:
def fill(ab: AnyRef, p: P): Unit = {
ab match {
case a: A => a.m(p)
case b: B => b.m(p)
}
}
但这涉及代码重复。我尝试用鸭子打字解决它,到目前为止,我对这个主题的最佳看法是:
type WithM[T] = { def m(p: P): T }
def fill[S, T <: WithM[S]](ab: T, p: P): S =
ab.m(p)
fill(new A, new P)
但我得到类型推断错误,如:
Error:(18, 5) inferred type arguments [Nothing,A] do not conform to method fill's type parameter bounds [S,T <: Test.WithM[S]]
fill(new A, new P)
^
这个问题可以用最少的魔法以优雅的方式解决吗?
答案 0 :(得分:11)
您有几个选择。一种是明确提供类型参数:
scala> fill[A, A](new A, new P)
A.m
res1: A = A@4beb8b21
如果m
方法始终返回其定义的类型的值,则可以通过在fill
中对该事实进行编码来帮助进行类型推断:
scala> def fill[T <: WithM[T]](o: T, p: P): T = o.m(p)
fill: [T <: WithM[T]](o: T, p: P)T
scala> fill(new A, new P)
A.m
res2: A = A@5f9940d4
您也可以跳过类型别名:
scala> def fill[S](o: { def m(o: P): S }, p: P): S = o.m(p)
fill: [S](o: AnyRef{def m(o: P): S}, p: P)S
scala> fill(new A, new P)
A.m
res3: A = A@3388156e
我强烈建议使用类型类,但它有点语法上的开销但更清晰:
trait HasM[T] {
type Out
def apply(t: T, p: P): Out
}
object HasM {
type Aux[T, Out0] = HasM[T] { type Out = Out0 }
implicit def AHasM: Aux[A, A] = new HasM[A] {
type Out = A
def apply(t: A, p: P): A = t.m(p)
}
implicit def BHasM: Aux[B, B] = new HasM[B] {
type Out = B
def apply(t: B, p: P): B = t.m(p)
}
}
def fill[T](t: T, p: P)(implicit hm: HasM[T]): hm.Out = hm(t, p)
然后:
scala> fill(new A, new P)
A.m
res4: A = A@74e92aa9
scala> fill(new B, new P)
B.m
res5: B = B@1ea35068
没有反思性的访问权限,而且您使用的是广泛理解的习语。
答案 1 :(得分:1)
你可以使用类型类,但老实说,在这种情况下,如果真的没有A
和B
的常见超类型,我只会模式匹配。
trait POps[T] {
def m(t: T, p: P): T
}
object POps {
def apply[T : POps] = implicitly[POps[T]]
}
object A {
implicit val aPops: POps[A] = new POps[A] {
def m(t: A, p: P) = t.m(p)
}
}
object B {
implicit val bPops: POps[B] = new POps[B] {
def m(t: B, p: P) = t.m(p)
}
}
def fill[M : POps](o: M, p: P): Unit = {
POps[M].m(o, p)
}
如果真的只有两个,那么只使用模式匹配。