F-bounded types and methods with type parameters at argument and return sites

175 Views Asked by At

I have an F-bounded type, and my objective is to create a type-parametrized method in order to be able to reuse it. Here is the example code:

trait FType {
  type ThisType <: FType

  def deepCopy(): ThisType

}

class ConcreteType extends FType {

  override type ThisType = ConcreteType

  override def deepCopy(): ConcreteType = this

}

class ConcreteType2 extends FType {

  override type ThisType = ConcreteType2

  override def deepCopy(): ConcreteType2 = this

}

object FType {

  def deepCopy[T <: FType](_type: T): T = {
    _type.deepCopy()
  }

/*  def deepCopy2(c: ConcreteType2): ConcreteType2 = {
    c.deepCopy()
  }*/

  def main(args: Array[String]): Unit = {
    //deepCopy2(new ConcreteType2)
  }

}

However, the code does not compile. The compiler throws this error:

Error:(29, 19) type mismatch;
 found   : _type.ThisType
 required: T
    _type.deepCopy()

I understand that it has something to to with path-dependent types since _type.ThisType is not the same type as T.

But how, then, can I take advantage of F-bounded types if the F-bounded type is not exactly the same as the type using the F-bounded type? If the types are not exactly the same, how is that deepCopy2 compiles?

Note: I know I can avoid using type parameters in deepCopy by using method overloading for each of the concrete types.

2

There are 2 best solutions below

0
On

In F-bounded types, you'd normally have ThisType as a type parameter instead of a type member:

trait FType[ThisType <: FType] {
  def deepCopy(): ThisType    
}

class ConcreteType extends FType[ConcreteType] {
  override def deepCopy(): ConcreteType = this
}

// in object FType
def deepCopy[T <: FType[T]](_type: T): T = {
  _type.deepCopy()
}

Note the difference: in FType.deepCopy the compiler knows that the return type of _type.deepCopy() is T.

You can do the same with type members:

def deepCopy[T <: FType { type ThisType <: T }](_type: T): T = 
  _type.deepCopy()
0
On

If the types are not exactly the same, how is that deepCopy2 compiles?

That is quite simple. It works because there is no polymorphism involved. It's statically known that ConcreteType2 has deepCopy() method that returns ConcreteType2. You could even remove type ThisType from whole hierarchy and it would still work the same way.


But how, then, can I take advantage of F-bounded types if the F-bounded type is not exactly the same as the type using the F-bounded type?

You need to tell the compiler that it's the same, as you haven't specified enough. Let's take a look at an example that works and is polymorphic:

def deepCopy[A <: FType { type ThisType = A }](_type: A): A = _type.deepCopy()
//                      ^ --important bit-- ^

This defines a method that works for any A that is FType and also it's type member ThisType is set to A, tying them together. That means it would work for your definitions of ConcreteType and ConcreteType2. It would NOT compile, however, for classes that aren't defined properly, such as this one:

class Bogus extends FType {
  override type ThisType = ConcreteType2
  override def deepCopy(): ConcreteType2 = new ConcreteType2
}

deepCopy(new Bogus)

Alternatively, let's start with a slightly modified version of your method:

def deepCopyPD[A <: FType](_type: A): _type.ThisType = _type.deepCopy()
                                   ^path-dependent^

It puts no constraints on ThisType, but in fact compiler would be able to infer proper versions of it for all cases:

val x: ConcreteType2 = deepCopyPD(new ConcreteType2)
val y: ConcreteType2 = deepCopyPD(new Bogus) // yep, this invocation is possible with such signature

However, it is also possible to further add constraints using type equality evidence as implicit parameter:

def deepCopyPD2[A <: FType](_type: A)(implicit ev: _type.ThisType =:= A): A = _type.deepCopy()

This, once more, forbids the invocation with Bogus