为什么需要模式匹配来保留存在的类型信息?

Why is pattern match needed to preserve existential type information?

一篇描述如何使用类型 classes 避免 F 有界多态性的博客(参见 Returning the "Current" Type in Scala)接近尾声时提到:

The problem here is that the connection between the types of p._1 and p._2 is lost in this context, so the compiler no longer knows that they line up correctly. The way to fix this, and in general the way to prevent the loss of existentials, is to use a pattern match.

我已验证提到的代码不起作用:

pets.map(p => esquire(p._1)(p._2))

而另一个模式匹配变体是:

pets.map { case (a, pa)  => esquire(a)(pa) }

还有另一种未提及的变体也有效:

pets.map{case p => esquire(p._1)(p._2)}

这里有什么魔力?为什么使用 case p => 而不是 p => 保留存在类型信息?

我已经用 Scala 2.12 和 2.13 测试过了。

Scastie link 玩码:https://scastie.scala-lang.org/480It2tTS2yNxCi1JmHx8w

问题需要针对 Scala 3 (Dotty) 进行修改,因为那里不再存在存在类型(双关语意)。它似乎即使没有 case 也能正常工作,正如另一个 scastie 所证明的那样:https://scastie.scala-lang.org/qDfIgkooQe6VTYOssZLYBg(你可以检查你仍然需要 case p 即使在 Scala 2.12 中有一个助手 class / 2.13 - 没有它你会得到一个编译错误。

使用帮助程序修改代码 case class:

case class PetStored[A](a: A)(implicit val pet: Pet[A])

val pets = List(PetStored(bob), PetStored(thor))

println(pets.map{case p => esquire(p.a)(p.pet)})

基于,考虑片段

pets.map { p =>
  val x = p._1
  val y = p._2
  esquire(x)(y)
}

类型检查后 -Xprint:typer 变为

Hello.this.pets.map[Any](((p: (A, example.Hello.Pet[A]) forSome { type A }) => {
  val x: Any = p._1;
  val y: example.Hello.Pet[_] = p._2;
  Hello.this.esquire[Any](x)(<y: error>)
}))

而带有模式匹配的代码段

pets.map { case (a, pa) =>
  val x = a
  val y = pa
  esquire(x)(y)
}

类型检查后变为

 Hello.this.pets.map[Any](((x0: (A, example.Hello.Pet[A]) forSome { type A }) => x0 match {
  case (_1: A, _2: example.Hello.Pet[A]): (A, example.Hello.Pet[A])((a @ _), (pa @ _)) => {
    val x: A = a;
    val y: example.Hello.Pet[A] = pa;
    Hello.this.esquire[A](x)(y)
  }
}));

我们注意到在后一种模式匹配情况下,存在类型参数A被重新引入

val x: A = a;
val y: example.Hello.Pet[A] = pa;

因此 xy 之间的关系重新建立,而在没有模式匹配的情况下,关系丢失

val x: Any = p._1;
val y: example.Hello.Pet[_] = p._2;