Search code examples
scalatypestype-systemsexistential-typef-bounded-polymorphism

Compilation issue in Scala with F-bounded types and existential types


I'm using a F-bounded type in order to be able to return the current type

trait Board[T <: Board[T]] {
  def updated : T
}

And I'm trying to write a generic helper method that use it.

The question is : why the following do not compile ?

object BoardOps {
  def updated(board: Board[_]) = {
    board.updated.updated
  }
}

The error is value updated is not a member of _$1

I've figured out these 2 workarounds. Are they equivalent ?

object BoardOps {
  def updated(board: Board[_<:Board[_]]) = {
    board.updated.updated
  }
}

object BoardOps {
  def updated[T <: Board[T]](board: T) : T = {
    board.updated.updated
  }
}

Solution

  • why the following do not compile?

    Using Board[_] as a parameter type tells the compiler "I do not care for the type of parameter inside board". Namely, this, to the compiler, is an existential type, it doesn't know any specifics about that type. As such, board.updated returns an "unspeakable", or opaque type, because we told the compiler to "throw out" that type information.

    I've figured out these 2 workarounds. Are they equivalent?

    Your former example uses an existential type with a constraint to be a subtype of Board[_], or more formally we write:

    Board[T] forSome { type T <: Board[U] }
    

    Where the compiler actually names T -> $_1 and U -> $_2

    Again, we know nothing of the inner type parameter, only that it has an upper bound on Board[_]. Your latter example uses a universally quantified type named T, which the compiler is able to use to deduce the return type of the method to be of a particular type T rather than Any.

    To answer your question, no, they are not equivalent. Existential and universally quantified types are dual to each other. More information on existentials can be found on What is an existential type? and https://www.drmaciver.com/2008/03/existential-types-in-scala/