scalagenericscode-reusereturn-typegeneric-variance

Scala - returning same type as passed as argument


Let's say I have a class M that classes A,B,C inherit:

abstract M
A extends M
B extends M
C extends M

And i should like to do something like this:

val a0:A = ...
val b0:B = ...
val c0:C = ...
val a1:A = transform[A](a0)
val b1:B = transform[B](b0)
val c1:C = transform[C](c0)

Where transform essentially does the same for each subtype and only differs in how the result object is constructed.

I have a hunch that's not possible and I need either copy code and create separate transform methods or resort to typecasting. Or is there a better way?

EDIT

Note that def transform[T<:M](t:T):T does not work. If we try to return an A,B or C, we will get the error messages below.

Expression of type A does not conform to expected type T

Expression of type B does not conform to expected type T

Expression of type C does not conform to expected type T

EDIT 2 Perhaps some more detailed information on what I'm trying to do:

transform(m:M) = {
    val P = Property P computed from m
    m match {
        case a:A => construct and return new A from a with property P
        case b:B => construct and return new B from b with property P
        case c:C => construct and return new C from c with property P
        case _ => error
    }
}

If I do it like that, then I need casts:

val a1:A = transform(a0).asInstanceOf[A]
val b1:B = transform(b0).asInstanceOf[B]
val c1:C = transform(c0).asInstanceOf[C]

which I should like to eliminate.


Solution

  • It depends on the implementation of transform. If it's clear to the compiler that transform preserves the type then @Dimitry's answer works.

    If you need less obvious (to the compiler) relationships between types then the usual way is the typeclass pattern.

    trait Transformer[T] {
      def transform(t: T) : T
    }
    
    def transform[T: Transformer](t: T) = implicitly[Transformer[T]].transform(t)
    
    implicit object ATransformer extends Transformer[A] {
      def transform(a: A): A = ...
    }
    implicit object BTransformer extends Transformer[B] {
      def transform(b: B): B = ...
    }
    

    Then you can implement the specific transforms for A/B/C in the specific objects, and the compiler will only allow you to call transform if you have a suitable Transformer in implicit scope.