在 Scala 中使用类型属性的目的是什么?

规范中没有太多关于什么是类型归属的信息,当然也没有任何关于其目的的内容。除了“使传递的 varargs 工作”之外,我将使用类型归属来做什么?下面是一些关于使用它的语法和效果的 scala REPL。

scala> val s = "Dave"
s: java.lang.String = Dave


scala> val p = s:Object
p: java.lang.Object = Dave


scala> p.length
<console>:7: error: value length is not a member of java.lang.Object
p.length
^
scala> p.getClass
res10: java.lang.Class[_ <: java.lang.Object] = class java.lang.String


scala> s.getClass
res11: java.lang.Class[_ <: java.lang.Object] = class java.lang.String


scala> p.asInstanceOf[String].length
res9: Int = 4
20246 次浏览

Type ascription is just telling the compiler what type you expect out of an expression, from all possible valid types.

A type is valid if it respects existing constraints, such as variance and type declarations, and it is either one of the types the expression it applies to "is a", or there's a conversion that applies in scope.

So, java.lang.String extends java.lang.Object, therefore any String is also an Object. In your example you declared you want the expression s to be treated as an Object, not a String. Since there is no constraints preventing that and the desired type is one of the types s is a, it works.

Now, why would you want that? Consider this:

scala> val s = "Dave"
s: java.lang.String = Dave


scala> val p = s: Object
p: java.lang.Object = Dave


scala> val ss = scala.collection.mutable.Set(s)
ss: scala.collection.mutable.Set[java.lang.String] = Set(Dave)


scala> val ps = scala.collection.mutable.Set(p)
ps: scala.collection.mutable.Set[java.lang.Object] = Set(Dave)


scala> ss += Nil
<console>:7: error: type mismatch;
found   : scala.collection.immutable.Nil.type (with underlying type object Nil)
required: java.lang.String
ss += Nil
^


scala> ps += Nil
res3: ps.type = Set(List(), Dave)

You could also have fixed this by type ascripting s at ss declaration, or you could have declared ss's type to be Set[AnyRef].

However, type declarations achieve the same thing only as long as you are assigning a value to an identifier. Which one can always do, of course, if one doesn't care about littering the code with one-shot identifiers. For example, the following does not compile:

def prefixesOf(s: String) = s.foldLeft(Nil) {
case (head :: tail, char) => (head + char) :: head :: tail
case (lst, char) => char.toString :: lst
}

But this does:

def prefixesOf(s: String) = s.foldLeft(Nil: List[String]) {
case (head :: tail, char) => (head + char) :: head :: tail
case (lst, char) => char.toString :: lst
}

It would be silly to use an identifier here in place of Nil. And though I could just write List[String]() instead, that isn't always an option. Consider this, for instance:

def firstVowel(s: String) = s.foldLeft(None: Option[Char]) {
case (None, char) => if ("aeiou" contains char.toLower) Some(char) else None
case (vowel, _) => vowel
}

For the reference, this is what Scala 2.7 spec (march 15, 2009 draft) has to say about type ascription:

Expr1 ::= ...
| PostfixExpr Ascription


Ascription ::= ‘:’ InfixType
| ‘:’ Annotation {Annotation}
| ‘:’ ‘_’ ‘*’

One possibility is when network and serial protocol level stuff, then this:

val x = 2 : Byte

is far cleaner than

val x = 2.asInstanceOf[Byte]

The second form is also a runtime conversion (not handled by the compiler) and could lead to some interesting over/underflow conditions.

You may find this thread illuminating, if a bit convoluted to follow. The important thing to note is that you're adding constraint hints to the type checker - it gives you a little more control over what that compilation phase is doing.

I use type ascription to paper over holes in Scala's type inference. For example, foldLeft over a collection of type A takes an initial element of type B and a function (B, A) => B that is used to fold the elements of the collection into the initial element. The actual value of type B is inferred from the type of the initial element. Since Nil extends List[Nothing], using it as an initial element causes problems:

scala> val x = List(1,2,3,4)
x: List[Int] = List(1, 2, 3, 4)


scala> x.foldLeft(Nil)( (acc,elem) => elem::acc)
<console>:9: error: type mismatch;
found   : List[Int]
required: scala.collection.immutable.Nil.type
x.foldLeft(Nil)( (acc,elem) => elem::acc)
^


scala> x.foldLeft(Nil:List[Int])( (acc,elem) => elem::acc )
res2: List[Int] = List(4, 3, 2, 1)

Alternatively, you could just use List.empty[Int] instead of Nil:List[Int].

scala> x.foldLeft(List.empty[Int])( (acc,elem) => elem::acc )
res3: List[Int] = List(4, 3, 2, 1)

edit: List.empty[A] is implemented as

override def empty[A]: List[A] = Nil

(source)

This is effectively a more verbose form of Nil:List[A]

Type Inference: We can skip Explicitly giving Name of Type of Something in source code, called Type Inference.(Although required in some exceptional cases.)

Type Ascription: Being explicit about the type of something is called a Type Ascription. What Difference It can make?

ex: val x = 2 : Byte

also see: 1. We can explicitly give return type to our functions

def t1 : Option[Option[String]] = Some(None)


> t1: Option[Option[String]]

Another way of declaring this could be:

def t2 = Some(None: Option[String])
> t2: Some[Option[String]]

Here we did not give Option[Option[String]] return type explicitly and Compiler inferred it as Some[Option[String]]. Why Some[Option[String]] is because we used type ascription in the definition.

  1. Another way we can use the same definition is:

    def t3 = Some(None)

    > t3: Some[None.type]

This time We did not explicitly tell the compiler anything(neither this defi). And It inferred our definition as Some[None.type]