Scala列出了存在类型:`map {case t => ......}`工作,`map {t => ......}`不是吗?

时间:2018-04-07 21:30:38

标签: scala types pattern-matching type-inference existential-type

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

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

然后定义了类型List[T]的列表:

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

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

list.map{ x => x._1(x._2) }

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

list.map{ case x => x._1(x._2) }

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

1 个答案:

答案 0 :(得分:1)

(我自己试图回答这个问题;应该不是太错了,但也许有点肤浅。)

首先,观察

list.map{ x => x._1(x._2) }
list.map{ case x => x._1(x._2) }

基本相同
list map f1
list map f2

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

确实,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 }应用于Any,因为某些(SuperSpecialType => SuperSpecialType)实际上可能会SuperSpecialType
  5. 因此,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)
        }
      }
    }
    

    然后使用

    生成类型检查器的输出
    $ scalac -Xprint:typer someFile.scala 
    

    我们基本上获得了(删除了一些噪音):

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

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

    f: X => X
    x: X
    

    这样f(x)确实有效。

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

    我们也可以写:

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

    或甚至更明确地说:

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

    并且两个版本的编译与f2的原因非常相似。