liam923
liam923

Reputation: 1021

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?

Upvotes: 1

Views: 199

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51658

One more option is to make T a type member rather than type parameter. In such case the existential type corresponds to just Container while a specific type is Container { type T = ... } (aka Container.Aux[...]). Unfortunately, type-member type can't be used in the class primary constructor

case class Container(value: T) { // not found: type T
  type T
  //...
}

so I'm replacing the case class with a trait + factory method

trait Container {
  type T
  def value: T
  def replace(value: T): Container.Aux[T] = Container(value)
}
object Container {
  type Aux[_T] = Container { type T = _T }
  // factory method
  def apply[_T](_value: _T): Aux[_T] = new Container {
    override type T = _T
    override val value: T = _value
  }
}

val x: Container = Container[Int](1)

x.replace(x.value) // compiles

def identity[T](container: Container.Aux[T]): Container.Aux[T] =
  container.replace(container.value)

identity[x.T](x) // compiles

Please notice that I made x a val rather than var so that the path-dependent type x.T makes sense.

Maybe you prefer to keep a case class because of all the syntax sugar the compiler generates for case classes. In such case we could introduce an additional trait

trait IContainer {
  type T
  def value: T
  def replace(value: T): IContainer.Aux[T]
}
object IContainer {
  type Aux[_T] = IContainer { type T = _T }
}

case class Container[_T](value: _T) extends IContainer {
  override type T = _T
  override def replace(value: T): Container[T] = Container(value)
}

val x: IContainer = Container[Int](1)

x.replace(x.value) // compiles

def identity[T](container: IContainer.Aux[T]): IContainer.Aux[T] =
  container.replace(container.value)

identity[x.T](x) // compiles

Upvotes: 1

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

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:

Upvotes: 3

Related Questions