0
votes

I'm learning about F-bound types in Scala, and I have come across a situation where I don't know what's wrong.

I've made three tests, the code is as follows:

import scala.collection.mutable

def test1() = {

  trait Abstract {
    type ThisType <: Abstract
    def deepCopy(): ThisType
  }

  case class Concrete1(a: Int) extends Abstract {
    override type ThisType = Concrete1
    override def deepCopy(): ThisType = this.copy()

  }

  case class Concrete2(a: Int) extends Abstract {
    override type ThisType = Concrete2
    override def deepCopy(): ThisType = this.copy()

  }

  val set = new mutable.HashSet[Abstract]()

  set ++= List(Concrete1(1), Concrete2(2))

  val set2: mutable.Set[Abstract] = set.map(_.deepCopy())

}

def test2() = {

  trait Abstract {
    type ThisType
    def deepCopy(): ThisType
  }

  case class Concrete1(a: Int) extends Abstract {
    override type ThisType = Concrete1
    override def deepCopy(): ThisType = this.copy()

  }

  case class Concrete2(a: Int) extends Abstract {
    override type ThisType = Concrete2
    override def deepCopy(): ThisType = this.copy()

  }

  val set = new mutable.HashSet[Abstract]()

  set ++= List(Concrete1(1), Concrete2(2))

  val set2: mutable.Set[Abstract] = set.map(_.deepCopy())

}

def test3() = {


  trait Abstract[T <: Abstract[T]] {
    def deepCopy(): T
  }

  case class Concrete1(a: Int) extends Abstract[Concrete1] {
    override def deepCopy(): Concrete1 = this.copy()

  }

  case class Concrete2(a: Int) extends Abstract[Concrete2] {
    override def deepCopy(): Concrete2 = this.copy()

  }

  val set = new mutable.HashSet[Abstract[_]]()

  set ++= List(Concrete1(1), Concrete2(2))

  val set2: mutable.Set[Abstract[_]] = set.map(_.deepCopy())

}

test1 works fine. test2 and test3 produce a compile-time error.

In test2, I ommit that ThisType is a subtype of Abstract. I understand that if I don't set this upper bound, ThisType can be anything. But if I have a Set of Abstract and I perform a deepCopy() of it's elements, wouldn't it be of the same type? The compiler produces this error:

Error:(53, 45) type mismatch;
 found   : scala.collection.mutable.HashSet[Abstract#ThisType]
 required: scala.collection.mutable.Set[Abstract]
  val set2: mutable.Set[Abstract] = set.map(_.deepCopy())
                                           ^

I don't understand why in this case (test2) Abstract#ThisType is not the same type as Abstract and in test1 is. Does it have something to do with path-dependent types? If so, what is the explanation?

In test3 I try to do de same as in test1 but with type parameters, this complier throws an error at line val set2: mutable.Set[Abstract[_]] = set.map(_.deepCopy()) saying:

Error:(78, 48) type mismatch;
 found   : scala.collection.mutable.HashSet[Any]
 required: scala.collection.mutable.Set[Abstract[_]]
Note: Any >: Abstract[_], but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ >: Abstract[_]`. (SLS 3.2.10)
  val set2: mutable.Set[Abstract[_]] = set.map(_.deepCopy())
                                              ^
Error:(140, 45) type mismatch;
 found   : scala.collection.mutable.HashSet[Abstract#ThisType]
 required: scala.collection.mutable.Set[Abstract]
  val set2: mutable.Set[Abstract] = set.map(_.deepCopy())
                                           ^
Error:(166, 48) type mismatch;
 found   : scala.collection.mutable.HashSet[Any]
 required: scala.collection.mutable.Set[Abstract[_]]
Note: Any >: Abstract[_], but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ >: Abstract[_]`. (SLS 3.2.10)
  val set2: mutable.Set[Abstract[_]] = set.map(_.deepCopy())
                                              ^

It's something related to wildcards, but I don't know how to declare such types without wildcards.

1
test3 will compile if def deepCopy(): Abstract[T]. No other changes needed, but it should be noted that without the "self type" (self :T => ) you're not getting the safety guarantees of F-bounded polymorphism.jwvh
@jwvh why is that?vicaba
Without the "self type" restriction, the compiler will allow class Concrete2(a: Int) extends Abstract[Concrete1] {.., which usually isn't what you want.jwvh

1 Answers

1
votes

heres a version that shows some parts from my explanation https://scalafiddle.io/sf/Wnk3ekK/2

so the problem in 2 scalac canno't prove that Abstract is a common supertype for the typemeber due to the missing bound. see that ThisType can e.g. be Int which indeed is not a subtype of Abstract and thus not eligible to be in Set[Abstract]

in 3 is the problem that Abstract[_] is an existential and that doesn't work quite like that so you could interject a common supertype as shown.