Avoid code duplication using duck print in scala

I am currently encoding scala and I consider myself a newbie. I have 3 classes that are not subject to control, which means that I can not change them.

class P class A { def m(p: P): A = { println("Am"); this } } class B { def m(p: P): B = { println("Bm"); this } } 

This is a simplified example that is really 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:

 def fill(ab: AnyRef, p: P): Unit = { ab match { case a: A => am(p) case b: B => bm(p) } } 

but this is due to code duplication. I tried to solve this problem with duck printing, and so far I am best suited to the topic:

 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 input errors like:

 Error:(18, 5) inferred type arguments [Nothing,A] do not conform to method fill type parameter bounds [S,T <: Test.WithM[S]] fill(new A, new P) ^ 

Is it possible to solve this problem in an elegant way with minimal magic?

+8
scala type-inference
source share
2 answers

You have several options. One of them is to explicitly specify type parameters:

 scala> fill[A, A](new A, new P) Am res1: A = A@4beb8b21 

If method m always returns the value of the type in which it is defined, you can help with the type inference by encoding this fact in your fill :

 scala> def fill[T <: WithM[T]](o: T, p: P): T = om(p) fill: [T <: WithM[T]](o: T, p: P)T scala> fill(new A, new P) Am res2: A = A@5f9940d4 

You can also skip an alias like:

 scala> def fill[S](o: { def m(o: P): S }, p: P): S = om(p) fill: [S](o: AnyRef{def m(o: P): S}, p: P)S scala> fill(new A, new P) Am res3: A = A@3388156e 

I would highly recommend using a type class, although these are slightly syntactic utility ones, 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 = tm(p) } implicit def BHasM: Aux[B, B] = new HasM[B] { type Out = B def apply(t: B, p: P): B = tm(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) Am res4: A = A@74e92aa9 scala> fill(new B, new P) Bm res5: B = B@1ea35068 

There is no reflexive access, and you are using a widely understood idiom.

+11
source share

You can use typeclass, but to be honest, in this case I would just match the pattern if there really isn’t the usual supertype A and 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) = tm(p) } } object B { implicit val bPops: POps[B] = new POps[B] { def m(t: B, p: P) = tm(p) } } def fill[M : POps](o: M, p: P): Unit = { POps[M].m(o, p) } 

If there really are only two, just use pattern matching.

+1
source share

All Articles