具有存在类型的 Scala 列表:`map{ case t => ... }` 有效,`map{ t => ... }` 无效?
Scala lists with existential types: `map{ case t => ... }` works, `map{ t => ... }` doesn't?
假设我们已经定义了一个存在类型:
type T = (X => X, X) forSome { type X }
然后定义了一个列表List[T]
:
val list = List[T](
((x: Int) => x * x, 42),
((_: String).toUpperCase, "foo")
)
众所周知 [], [] 以下对 map
的尝试不起作用:
list.map{ x => x._1(x._2) }
但是,为什么下面的方法有效?:
list.map{ case x => x._1(x._2) }
请注意,两个链接问题的答案都假设模式匹配中需要类型变量,但它也可以在没有类型变量的情况下工作。问题的重点更多是为什么{ case x => ... }
有效?。
(我自己的尝试回答问题;应该不会太错误,但可能有点肤浅。)
首先,观察
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
的编译为什么会失败:
t
的类型是 (X => X, X) forSome { type X }
- 因此,第一个组件
t._1
被推断为类型 (X => X) forSome { type X }
。
- 同样,第二个组件
t._2
被推断为 X forSome { type X }
类型,即 Any
.
- 我们不能将
(X => X) forSome { type X }
应用于 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)
}
}
}
然后用
生成类型检查器的输出
$ 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
的脱糖和type-checked代码中,我们看到X
类型奇迹般地重新出现了,typechecker确实推断:
f: X => X
x: X
所以 f(x)
确实有效。
在具有显式命名类型变量的更明显的 work-around 中,我们手动执行编译器在这种情况下为我们执行的操作。
我们也可以这样写:
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
.
非常相似
假设我们已经定义了一个存在类型:
type T = (X => X, X) forSome { type X }
然后定义了一个列表List[T]
:
val list = List[T](
((x: Int) => x * x, 42),
((_: String).toUpperCase, "foo")
)
众所周知 [map
的尝试不起作用:
list.map{ x => x._1(x._2) }
但是,为什么下面的方法有效?:
list.map{ case x => x._1(x._2) }
请注意,两个链接问题的答案都假设模式匹配中需要类型变量,但它也可以在没有类型变量的情况下工作。问题的重点更多是为什么{ case x => ... }
有效?。
(我自己的尝试回答问题;应该不会太错误,但可能有点肤浅。)
首先,观察
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
的编译为什么会失败:
t
的类型是(X => X, X) forSome { type X }
- 因此,第一个组件
t._1
被推断为类型(X => X) forSome { type X }
。 - 同样,第二个组件
t._2
被推断为X forSome { type X }
类型,即Any
. - 我们不能将
(X => X) forSome { type X }
应用于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)
}
}
}
然后用
生成类型检查器的输出$ 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
的脱糖和type-checked代码中,我们看到X
类型奇迹般地重新出现了,typechecker确实推断:
f: X => X
x: X
所以 f(x)
确实有效。
在具有显式命名类型变量的更明显的 work-around 中,我们手动执行编译器在这种情况下为我们执行的操作。
我们也可以这样写:
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
.