具有生存类型的Scala列表:`map {case t => ...}`有效,`map {t => ...}`不是吗? [英] Scala lists with existential types: `map{ case t => ... }` works, `map{ t => ... }` doesn't?
问题描述
假设我们定义了一个存在类型:
Suppose that we have defined an existential type:
type T = (X => X, X) forSome { type X }
,然后定义类型为List[T]
的列表:
and then defined a list of type List[T]
:
val list = List[T](
((x: Int) => x * x, 42),
((_: String).toUpperCase, "foo")
)
众所周知[ 1 ],[ 2 ],尝试进行map
的以下尝试无效:
It is well known [1], [2] that the following attempt to map
does not work:
list.map{ x => x._1(x._2) }
但是,为什么下面的方法起作用?
But then, why does the following work?:
list.map{ case x => x._1(x._2) }
请注意,两个链接问题的答案都假定在模式匹配中需要类型变量,但在没有类型变量的情况下也可以使用.问题的重点更多地放在 { case x => ... }
为什么起作用?.
Note that answers to both linked questions assumed that a type variable is required in the pattern matching, but it also works without the type variable. The emphasis of the question is more on Why does the { case x => ... }
work?.
推荐答案
(我自己试图回答这个问题;应该不太错,但可能有点肤浅.)
首先,请注意
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
的编译成功.
Indeed, compilation of f1
fails, whereas f2
succeeds.
我们可以看到为什么f1
的编译必须失败:
We can see why the compilation of f1
has to fail:
-
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)
.
t
is of type(X => X, X) forSome { type X }
- Therefore, the first component
t._1
is inferred to have the type(X => X) forSome { type X }
. - Likewise, the second component
t._2
is inferred to have the typeX forSome { type X }
, which is justAny
. - We cannot apply an
(X => X) forSome { type X }
toAny
, because it actually could turn out to be(SuperSpecialType => SuperSpecialType)
for someSuperSpecialType
.
因此,f1
的编译应该会失败,并且的确会失败.
Therefore, compilation of f1
should fail, and it indeed does fail.
要了解为什么f2
成功编译,可以查看typechecker的输出.如果我们将其另存为someFile.scala
:
To see why f2
compiles successfully, one can look at the output of the typechecker. If we save this as 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
我们获得了本质上(去除了一些噪音):
we obtain essentially (with some noise removed):
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
的经过删除和类型检查的代码中,我们看到X
类型奇迹般地出现了,而类型检查器确实推断出了:
The second f2_explicit_func_arg
version (equivalent to f2
) is more enlightening than the shorter original f2
-version. In the desugared and type-checked code of f2_explicit_func_arg
, we see that the type X
miraculously reappears, and the typechecker indeed infers:
f: X => X
x: X
所以f(x)
确实有效.
在使用显式命名的类型变量的更明显的解决方法中,在这种情况下,我们手动执行编译器为我们执行的操作.
In the more obvious work-around with an explicitly named type variable, we do manually what the compiler does for us in this case.
我们也可以写:
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
完全相同.
and both versions would compile for very much the same reasons as f2
.
这篇关于具有生存类型的Scala列表:`map {case t => ...}`有效,`map {t => ...}`不是吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!