推断
尽可能使用类型推断,但清晰度优先,并且在公共 API 中优先考虑显式性。
通常情况下,私有字段或局部变量的类型会立即在其值中显现,因此几乎不需要注释它们。
private val name = "Daniel"
但是,对于赋值值形式复杂或不明显的情况,你可能仍然希望显示类型。
所有公共方法都应具有显式类型注释。在这些情况下,类型推断可能会破坏封装,因为它依赖于内部方法和类详细信息。如果没有显式类型,对方法或 val 内部结构的更改可能会在不发出警告的情况下更改类的公共 API,从而可能破坏客户端代码。显式类型注释还可以帮助缩短编译时间。
函数值
函数值支持一种特殊情况的类型推断,值得单独说明
val ls: List[String] = ...
ls.map(str => str.toInt)
在 Scala 已经知道我们声明的函数值类型的情况下,无需注释参数(在本例中为 str
)。这是一个非常有用的推断,并且应尽可能优先使用。请注意,对函数值进行操作的隐式转换将使此推断无效,从而强制显式注释参数类型。
注释
类型注释应按照以下模板进行设计
value: Type
这是大多数 Scala 标准库和所有 Martin Odersky 示例采用的样式。值和类型之间的空格有助于眼睛准确地解析语法。将冒号放在值末尾而不是类型开头的原因是为了避免在这种情况下的混淆
value :::
这实际上是有效的 Scala,声明一个值是类型 ::
。显然,前缀样式注释冒号会极大地混淆事物。
归属
类型归属通常与类型注释混淆,因为 Scala 中的语法是相同的。以下是归属的示例
Nil: List[String]
Set(values: _*)
"Daniel": AnyRef
归属基本上只是在编译时执行向上转换以满足类型检查器。它的使用并不常见,但偶尔会发生。归属最常见的情况是使用单个 Seq
参数调用 varargs 方法。这是通过归属 _*
类型(如上面的第二个示例)来完成的。
归属遵循类型注释约定;冒号后面有一个空格。
函数
函数类型应在参数类型、箭头和返回类型之间用空格声明
def foo(f: Int => String) = ...
def bar(f: (Boolean, Double) => List[String]) = ...
应尽可能省略括号(例如,arity-1 方法,例如 Int => String
)。
Arity-1
Scala 具有用于声明 arity-1 函数类型的特殊语法。例如
def map[B](f: A => B) = ...
具体来说,可以从参数类型中省略括号。因此,我们没有声明 f
的类型为 (A) => B
,因为这将是不必要的冗长。考虑更极端的例子
// wrong!
def foo(f: (Int) => (String) => (Boolean) => Double) = ...
// right!
def foo(f: Int => String => Boolean => Double) = ...
通过省略括号,我们节省了整整六个字符,并极大地提高了类型表达式的可读性。
结构类型
如果结构类型长度小于 50 个字符,则应将其声明在一行上。否则,应将其拆分到多行,并(通常)分配给自己的类型别名
// wrong!
def foo(a: { def bar(a: Int, b: Int): String; val baz: List[String => String] }) = ...
// right!
private type FooParam = {
val baz: List[String => String]
def bar(a: Int, b: Int): String
}
def foo(a: FooParam) = ...
可以声明和内联使用更简单的结构类型(小于 50 个字符)
def foo(a: { val bar: String }) = ...
在内联声明结构类型时,每个成员应以分号和单个空格分隔,左花括号应后接一个空格,而右花括号应前置一个空格(如以上两个示例中所示)。
结构类型在运行时通过反射实现,并且本质上比标称类型性能更低。除非结构类型提供明确的好处,否则开发人员应优先使用标称类型。