1

I struggle to understand Scala's covariance in combination with lower bounds. I will illustrate my confusion in the following code snippet with 2 compilation errors.

class Queue[+T]:
  def enqueue[U >: T](x: U): Queue[U] = null

class IntQueue extends Queue[Int]:
  override def enqueue[Int](x: Int): Queue[Int] =
    println(math.sqrt(x)) // 1) Found: (x : Int) Required: Double
    super.enqueue(x)      // 2) Found: Queue[Any] Required: Queue[Int]

First, there is a generic class Queue which takes one type parameter with covariance annotation +. This is OK as I want to make assignments such as val a: Queue[Any] = IntQueue(). Its enqueue method has a lower bound for its type parameter, U >: T. This is needed because the x would otherwise be in a contravariant position, allowing for nasty things, similar to ArrayStoreException in Java's Array. Second, there is a parametrized class IntQueue generated by Queue with specific type being Int.

Questions – why the 1) and 2) compiler errors happen

ad 1)

  • My reasoning is that math.sqrt is defined as def sqrt(x: Double): Double. My argument's type, x, is of type Int. In this case, implicit conversion Int->Long->Float->Double should happen. Why does the compiler doesn't perform implicit conversion?
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Talos
  • 457
  • 4
  • 15
  • Since an `IntQueue` could be lifted as a `Queue[Any]` then you must be able to receive anything, not only `Int`, on `enqueue` and probably return something different than `IntQueue` – Luis Miguel Mejía Suárez Jan 15 '23 at 15:33
  • 2
    Switch on `-Xlint:type-parameter-shadow` https://stackoverflow.com/questions/54443888/what-causes-this-type-error-in-a-pattern-guard-for-matching-list-of-tuples https://stackoverflow.com/questions/57718070/type-parameters-applied-to-scala-function https://stackoverflow.com/questions/74725562/type-arguments-and-bounds-in-scala https://stackoverflow.com/questions/74779586/returning-functions-that-check-for-equality-ordinal-comparison-for-any-valid-typ – Dmytro Mitin Jan 15 '23 at 16:39
  • https://stackoverflow.com/questions/3751823/two-type-parameters-with-the-same-name https://stackoverflow.com/questions/17090756/scala-really-weird-type-mismatch https://stackoverflow.com/questions/10178377/java-generics-the-type-parameter-string-is-hiding-the-type-string – Dmytro Mitin Jan 15 '23 at 16:47
  • Correct overriding for `T=Int` would be https://scastie.scala-lang.org/DmytroMitin/h1aJbMcATGmIF4XrfD3bTg/2 Now the error is `Found: (x : U); Required: Double` – Dmytro Mitin Jan 15 '23 at 16:53
  • https://stackoverflow.com/questions/56598220/strange-error-with-string-in-scala-2-12-7 – Dmytro Mitin Jan 15 '23 at 17:00

1 Answers1

3

In your overriding method definition, Int is not what you expect. You're actually defining a type parameter called Int but not referring to the "original" Int type and thus "hiding" the original Int type.

You actually wrote the same as:

override def enqueue[I](x: I): Queue[I] = ...

Declare it as a non overriding method to achieve what you expect:

def enqueue(x: Int): Queue[Int] = ...
Gaël J
  • 11,274
  • 4
  • 17
  • 32
  • so If I understand it correctly, you implicitly specify what type parameter `U` is inside `IntQueue.enqueue` by declaring what its argument type is – `x: Int` – Talos Jan 15 '23 at 19:18
  • 2
    You only implicitly define that `U >: Int` but you cannot define anything stronger, that would violate the contract of `Queue` which is that you can enqueue any other element with is a super type of `Int`. That's why you cannot define this method with `override`. It must be another method. – Gaël J Jan 15 '23 at 19:53
  • you can define method with `override`: `override def enqueue[U >: Int](x: U): Queue[U]` compiles just fine – Talos Jan 16 '23 at 09:26
  • 2
    @Talos, yes you can but that's not what you expected in the 1st place: the method can be called with anything which is a supertype of `Int` and you will return a `QUeue[U]`, not a `IntQUeue`. – Gaël J Jan 16 '23 at 10:03