Scala中通配符类型参数的绑定

8zzbczxx  于 2023-02-08  发布在  Scala
关注(0)|答案(2)|浏览(175)

在Scala 2中,你当然可以使用通配符或存在类型作为类型参数。然而,这意味着你并不总是有一个你想使用的类型的名称。这有时会导致奇怪的情况,你需要依靠类型推理来避免显式地编写类型。
下面是一个有点做作的例子来说明我的意思:

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)

有没有更简洁的方法来解决这个问题?如果你能写这样的话,那就太好了:

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

据我所知,Scala中不存在这样的功能。我是不是遗漏了什么功能?还有,有人知道其他语言中有类似的功能吗?

3htmauhk

3htmauhk1#

使用类型模式匹配(使用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)
}

实际的类型本身在代码中没有名称,并且实际上不可见,但基本上发生的是这样的:

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)
  }
}

类型模式t绑定存在量化类型,并且可以在后续表达式中使用,从而可以对v: t进行类型化,并且也可以对c.replace(v)进行正确类型化。
另见以下相关问题:

  • 存在类型列表上的Map
  • 为什么这个包含模式匹配和更高类型的代码片段在Scala 2.12中不再编译?
fbcarpbf

fbcarpbf2#

还有一种选择是将T作为类型成员而不是类型参数。在这种情况下,存在类型只对应于Container,而特定类型是Container { type T = ... }(又名Container.Aux[...])。不幸的是,类型成员类型不能用于类主构造函数

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

所以我用trait + factory方法替换了case类

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

请注意,我将x设置为val而不是var,这样依赖于路径的类型x.T才有意义。
也许你更喜欢保留case类,因为编译器为case类生成了很多语法糖,在这种情况下,我们可以引入一个额外的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

相关问题