scalagenericstypestype-inferenceexistential-type

Bind wildcard type argument in Scala


In Scala 2, you can of course use use wildcard or existential types as type arguments. However, this means that you do not always have a name for a type you'd like to use. This sometimes leads to odd situations where you need to lean on type inference to circumvent writing a type explicitly.

Here is a somewhat contrived example of what I mean:

case class Container[T](value: T) {
  def replace(value: T): Container[T] = Container(value)
}

def identity[T](container: Container[T]): Container[T] = {
  // A weird way of writing the identity function,
  // but notice that I have essentially given the name
  // `T` to the `_`
  container.replace(container.value)
}

var x: Container[_] = Container[Int](1)

// This works, but as far as I know, there's no way to explicitly
// pass the type for `T`. For example, something like identity[_](x) won't work.
identity(x)

// This also fails to work, but if we could assign a name to the `_`, like `T`,
// then it would become obvious that this should work.
// x.replace(x.value)

Is there a way to get around this more cleanly? It would be really great if you could write something like:

let Container[T] = x.type in {
  // Now there is a type T in this scope,
  // and x has type `Container[T]`
}

As far as I'm aware, nothing of the sort exists in Scala. Is there a feature I'm missing. Also, does anyone know of similar features in other languages?


Solution

  • Use type pattern matching (tested with 2.13):

    case class Container[T](value: T) {
      def replace(value: T): Container[T] = Container(value)
    }
    
    val x: Container[_] = Container[Int](1)
    
    val y: Container[_] = x match {
      case c => c.replace(c.value)
    }
    

    The actual type itself does not have a name in code, and isn't actually visible, but what's basically happening is this:

    case class Container[T](value: T) {
      def replace(value: T): Container[T] = Container(value)
    }
    
    val x: Container[_] = Container[Int](1)
    
    val y: Container[_] = x match {
      case c: Container[t] =>{
        val v: t = c.value
        c.replace(v)
      }
    }
    

    The type pattern t binds the existentially quantified type, and can be used in subsequent expressions, so that v: t can be typed, and c.replace(v) is also properly typed.


    See also the following related questions: