具有存在类型的 Scala 列表:`map{ case t => ... }` 有效,`map{ t => ... }` 无效?

And*_*kin 6 types scala type-inference pattern-matching existential-type

假设我们已经定义了一个存在类型:

type T = (X => X, X) forSome { type X }
Run Code Online (Sandbox Code Playgroud)

然后定义了一个类型列表List[T]

val list = List[T](
  ((x: Int) => x * x, 42),
  ((_: String).toUpperCase, "foo")
)
Run Code Online (Sandbox Code Playgroud)

众所周知 [ 1 ]、[ 2 ] 以下尝试map不起作用:

list.map{ x => x._1(x._2) }
Run Code Online (Sandbox Code Playgroud)

但是,为什么以下工作?:

list.map{ case x => x._1(x._2) }
Run Code Online (Sandbox Code Playgroud)

请注意,两个链接问题的答案都假定模式匹配中需要一个类型变量,但它也可以在没有类型变量的情况下工作。问题的重点更多是为什么{ case x => ... }工作?.

And*_*kin 4

(我自己尝试回答这个问题;应该不会太错,但可能有点肤浅。)


首先,观察

list.map{ x => x._1(x._2) }
list.map{ case x => x._1(x._2) }
Run Code Online (Sandbox Code Playgroud)

本质上是一样的

list map f1
list map f2
Run Code Online (Sandbox Code Playgroud)

val f1: T => Any = t => t._1(t._2)
val f2: T => Any = _ match {
  case q => q._1(q._2)
}
Run Code Online (Sandbox Code Playgroud)

确实,编译f1失败,而编译f2成功。

我们可以看到为什么编译f1会失败:

  1. t属于类型(X => X, X) forSome { type X }
  2. 因此,推断第一个组件的t._1类型为(X => X) forSome { type X }
  3. 同样,第二个组件t._2被推断为具有类型X forSome { type X },即Any
  4. 我们不能应用(X => X) forSome { type X }to Any,因为它实际上可能适用(SuperSpecialType => SuperSpecialType)于某些人SuperSpecialType

因此,编译f1应该会失败,而且确实失败了。


要了解f2编译成功的原因,可以查看类型检查器的输出。如果我们将其保存为someFile.scala

class O {
  type T = (X => X, X) forSome { type X }

  def f2: T => Any = t => t match {
    case q => q._1(q._2)
  }

  def f2_explicit_func_arg: T => Any = t => t match {
    case q => {
      val f = q._1
      val x = q._2
      f(x)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后生成类型检查器的输出

$ scalac -Xprint:typer someFile.scala 
Run Code Online (Sandbox Code Playgroud)

我们基本上得到(去除一些噪音):

class O extends scala.AnyRef {
  type T = (X => X, X) forSome { type X };
  def f2: O.this.T => Any = ((t: O.this.T) => t match {
    case (q @ _) => q._1.apply(q._2)
  });
  def f2_explicit_func_arg: O.this.T => Any = ((t: O.this.T) => t match {
    case (q @ _) => {
      val f: X => X = q._1;
      val x: X = q._2;
      f.apply(x)
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

第二个f2_explicit_func_arg版本(相当于f2)比较短的原始f2版本更具启发性。在脱糖和类型检查的代码中f2_explicit_func_arg,我们看到类型X奇迹般地重新出现,并且类型检查器确实推断出:

f: X => X
x: X
Run Code Online (Sandbox Code Playgroud)

所以这f(x)确实是有效的。


在使用显式命名类型变量的更明显的解决方法中,我们手动执行编译器在这种情况下为我们所做的事情。

我们也可以这样写:

type TypeCons[X] = (X => X, X)
list.map{ case t: TypeCons[x] => t._1(t._2) }
Run Code Online (Sandbox Code Playgroud)

或者更明确地说:

list.map{ case t: TypeCons[x] => {
  val func: x => x = t._1
  val arg: x = t._2
  func(arg)
}}
Run Code Online (Sandbox Code Playgroud)

两个版本的编译原因都与f2.

  • @AndreyTyukin 我所说的“起点”是指这是一个相当长的系列的第一篇文章,如果我没记错的话,在一个或多个帖子中有些内容与这个问题接近。 (2认同)