Type Inference

来源:互联网 发布:阿里云域名优惠码 编辑:程序博客网 时间:2024/04/30 04:40

1. Overview

Some functional programming languages, like Haskell, can infer almost all types, because they can perform global type inference. Scala can’t do this, in part because Scala has to support subtype polymorphism (inheritance), which makes type inference much harder.

2. When Explicit Type Annotations Are Required

In practical terms, you have to provide explicit type annotations for the following situations:

  1. A mutable var or immutable val declaration where you don’t assign a value, (e.g.,abstract declarations in a class like val book: String, var count: Int).
  2. All method parameters (e.g., def deposit(amount: Money) = {…}).
  3. Method return types in the following cases:
    (a) When you explicitly call return in a method (even at the end).
    (b) When a method is recursive.
    (c) When two or more methods are overloaded (have the same name) and one of them calls another; the calling method needs a return type annotation.
    (d) When the inferred return type would be more general than you intended, e.g.,Any.(This case somewhat rare, fortunately.)

3. Case examples

(1) Case 3(c)

For case 3(c), we have the following example:

package com.brown/**  * Created by BrownWong on 2016/9/29.  */object StringUtilV1 {  def joiner(strings: String*): String = strings.mkString(" ")  def joiner(strings: List[String]): String = joiner(strings :_*)}object Hello {  def main(args: Array[String]): Unit = {    println(StringUtilV1.joiner("S", "C", "A", "L", "A"))    println(StringUtilV1.joiner(List("S", "C", "A", "L", "A")))  }}

output

S C A L AS C A L A

Explanation

  1. Because the second joiner method calls the first, it requires an explicit String return type.
  2. Scala supports methods that take variable argument lists (sometimes just called variadic methods). The first joiner method has this signature:

    def joiner(strings: String*): String = strings.mkString(" ")

    The * after the String in the argument list says “zero or more Strings.”
  3. The second joiner uses a special syntax to tell the compiler to convert the input List into the variable argument list expected by the first joiner method:

    def joiner(strings: List[String]): String = joiner(strings :_*)

    strings :_* is to think of it as a hint to the compiler that you want the list strings to be treated as a variable argument list ( * ) of some unspecified.

(2) Case 3(d)

For case 3(d), we have the following example:

package com.brown/**  * Created by BrownWong on 2016/9/29.  */object Hello {  def makeList(strings: String*) = {    if (strings.isEmpty)      List(0)    else      strings.toList  }  def main(args: Array[String]): Unit = {    val list: List[String] = makeList()  // Type mismatch  }}

We intended for makeList to return a List[String], but when strings.length equals zero, we return List(0).
Instead, we should return List.empty[String] or the special “marker” type for empty lists, Nil.

When the if clause returns List[Int] and the else clause returns List[String] (the result of strings.toList), the only possible inferred return type for the method is the closest common supertype of List[Int] and List[String], which is List[Any].

Return type is added :

package com.brown/**  * Created by BrownWong on 2016/9/29.  */object Hello {  def makeList(strings: String*): List[String] = {    if (strings.isEmpty)      Nil    else      strings.toList  }  def main(args: Array[String]): Unit = {    val list: List[String] = makeList("Brown", "Wong")    println(list)  }}

output

List(Brown, Wong)

4. Common typing mistake and experience

(1) Experience

When developing APIs that are built separately from their clients, declare method return types explicitly and use the most general return type you can. This is especially important when APIs declare abstract methods.

(2) Common typing mistake

Let’s see the following example:

scala> def double(i: Int) { 2 * i }double: (i: Int)Unitscala> println(double(2))()

Why did the second command print () instead of 4? Look carefully at what the scala interpreter said about the method signature: double (Int)Unit. We thought we defined a method named double that takes an Int argument and returns a new Int with the value “doubled,” but it actually returns Unit. Why?

The cause of this unexpected behavior is a missing equals sign in the method definition.Here is the definition we actually intended:

scala> def double(i: Int) = { 2 * i }double: (i: Int)Intscala> println(double(2))4

There is a reason for this behavior.
Scala regards a method with the equals sign before the body as a function definition and a function always returns a value in functional programming.
On the other hand, when Scala sees a method body without the leading equals sign, it assumes the programmer intended the method to be a “procedure” definition, meant for performing side effects only with the return value Unit. In practice, it is more likely that the programmer simply forgot to insert the equals sign!

Note
This behavior is too subtle and the mistake is easy to make. Because it is easy enough to define a function that returns Unit, the “procedure” syntax is now deprecated as of Scala 2.11. Don’t use it!

5. Unit type

We said before that Unit behaves like void in other languages. However, unlike void, Unit is actually a type with a single value, named (), which is a historical convention in functional languages.


Ref

《Programming Scala》

0 0