通过first problem of the 99 Scala Puzzles工作,我定义了我自己的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]
的末尾?)
如果编译器能够搞清楚,那么以这种方式设计语言的理由是什么?
答案 0 :(得分:1)
A
出现3次:
last[A]
List[A]
: A
(在参数列表之后)
需要第二个指定List
包含A
类型的对象。需要第3个指定函数返回类型为A
的对象。
第一个是您实际声明A
的地方,因此可以在其他两个地方使用。
答案 1 :(得分:1)
您需要撰写last[A]
,因为A
不存在。由于它不存在,通过在函数名称之后声明它,您实际上有机会为此类型定义一些期望或约束。
例如:last[A <: Int]
强制执行以下事实:A必须是Int
的子类型
声明后,您可以使用它来定义参数类型和返回类型。
答案 2 :(得分:1)
我从@ Lee的评论中得到了一个见解:
编译器如何知道列表[A]中的A不引用 实际类型叫A?
为了证明这是有道理的,我尝试用实际类型A
的名称替换类型变量String
,然后传递函数a List[Int]
,当last
被声明为def last[String](xs: List[String]): String
时,我能够通过last
一个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
。
如果编译器只是假设不在范围内的任何标识符是类型变量,那么它也会使调试变得更加困难。因此,必须在函数定义的开头声明它。