I am currently coding in scala and I consider my self a newbie. I have 3 classes that are out of my control, that means that I can't change them.
class P
class A {
def m(p: P): A = { println("A.m"); this }
}
class B {
def m(p: P): B = { println("B.m"); this }
}
This is a simplified example the actual code is more complicated and classes A, B have many other similar methods.
I need to call method m for instances of classes A, B
The obvious solution is:
def fill(ab: AnyRef, p: P): Unit = {
ab match {
case a: A => a.m(p)
case b: B => b.m(p)
}
}
but that involves code duplication. I tried to solve it with duck typing and so far my best take on the subject is this:
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)
but I get type inference errors like:
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)
^
Can this problem be solved in an elegant way with minimal magic?
You've got a few options. One is to provide the type parameters explicitly:
scala> fill[A, A](new A, new P)
A.m
res1: A = A@4beb8b21
If the m
method always returns a value of the type that it's defined on, you can help out the type inference by encoding that fact in your 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
You can also skip the type alias:
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
I'd strongly suggest using a type class, though—it's a little bit of syntactic overhead but much cleaner:
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)
And then:
scala> fill(new A, new P)
A.m
res4: A = A@74e92aa9
scala> fill(new B, new P)
B.m
res5: B = B@1ea35068
No reflective access and you're using a widely-understood idiom.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With