从处理99个Scala谜题的第一个问题开始,我定义了自己的last
版本,如下所示:
def last[A](xs: List[A]): A = xs match {
case x :: Nil => x
case x :: xs => last(xs)
}
我的问题是:为什么last
后面必须直接跟类型变量,就像last[A]
一样?如果我这样写函数,为什么编译器不能做正确的事情:
def last(xs: List[A]): A
.....
(使[A]
离开last[A]
的末端?)
如果编译器能够弄清楚,那么以这种方式设计语言的理由是什么?
A
出现3次:
-
last[A]
-
List[A]
-
: A
(参数列表后)
第二个需要指定List
包含类型为A
的对象。第三个需要指定函数返回类型为A
的对象。
第一个是实际声明A
的地方,所以它可以在其他两个地方使用。
您需要写入last[A]
,因为A
不存在。由于它不存在,通过在函数名称后面声明它,您实际上有机会定义该类型的一些期望或约束。
例如:last[A <: Int]
以强制执行A必须是Int
的子类型的事实
一旦声明了它,就可以使用它来定义参数的类型和返回类型。
我从@Lee的评论中得到了一个见解:
编译器怎么知道List[A]中的A没有引用实际类型叫做A?
为了向自己证明这是有意义的,我尝试用实际类型String
的名称替换类型变量A
,然后传递函数a List[Int]
,看到当last
被声明为类似于def last[String](xs: List[String]): String
时,我能够传递last
a List[Int]
:
scala> def last[String](xs: List[String]): String = xs match {
| case x :: Nil => x
| case x :: xs => last(xs)
| }
last: [String](xs: List[String])String
scala> last(List(1,2,3,4))
res7: Int = 4
因此,证明标识符String
的行为确实像类型变量,并且不引用具体类型String
。
如果编译器只是假设任何不在范围内的标识符都是类型变量,那么调试也会变得更加困难。因此,必须在函数定义的开头声明它是有意义的。