Type Inference

Like any statically typed language, Scala verifies types of objects at compile time. At the same time, it does not require you to state the obvious; it can infer types. You can use type inference for both simple types and generics.

Type Inference for Simple Types

We’ll first explore type inference for simple types. Let’s start with a piece of code where we indicate the type, as we’re used to doing in languages like C++ and Java:

MakingUseOfTypes/DefiningVariableWithType.scala
 
val​ greet : ​String​ = ​"Ahoy!"

We defined a variable named greet, mentioned its type is String, and gave it a value. Unlike Java, where you specify the type and then the variable name, in Scala you do the reverse—for two reasons: First, by asking us to place the type after the name, Scala alludes that picking a good variable name is more important than mentioning type. Second, the type information is optional.

Let’s take a look at the variable definition. From the value we assigned to the variable, the type of the variable is very clear. This is a case of no ambiguity, so mentioning the type is redundant. Since Scala can figure out the obvious type, let’s rewrite the previous definition, without the type detail:

MakingUseOfTypes/DefiningVariable.scala
 
val​ greet = ​"Ahoy!"

At compile time Scala will determine the type of a variable if you did not specify it and if there’s no ambiguity. In the previous example, the type of greet was inferred to be a String. We can check this in three ways. First, let’s ask Scala for the type information, in code:

MakingUseOfTypes/DefiningVariable.scala
 
println(greet)
 
println(greet.getClass)

We printed the value in the variable as well as its type. Let’s see what Scala reports:

 
Ahoy!
 
class java.lang.String

The output confirms that the type of the variable is the type we’d expect. However, the example does not confirm that Scala in fact inferred this at compile time and not runtime. A look at the bytecode generated by the compiler will be a sure way to tell. Let’s place the code in a class and compile it:

MakingUseOfTypes/TypeInference.scala
 
class​ TypeInference {
 
val​ greet = ​"Ahoy!"
 
}

Use the following commands to compile the code and view the details in the bytecode:

 
scalac -d bin TypeInference.scala
 
javap -classpath bin -private TypeInference

Here’s the output from the javap tool, confirming that the variable is of the desired type:

 
Compiled from "TypeInference.scala"
 
public class TypeInference {
 
private final java.lang.String greet;
 
public java.lang.String greet();
 
public TypeInference();
 
}

Viewing the bytecode is a sure way to check on what the Scala compiler generated, but that takes effort. If you want a quick confirmation of the type, Scala REPL is your friend—see Using the REPL. Type the command scala on the command line to invoke the REPL and then type the definition. Let’s key the example definition into the REPL:

 
scala> val greet = "Ahoy!"
 
greet: String = Ahoy!
 
 
scala> :quit

The REPL gives a quick feedback of the value and the type of the variable we created.

That’s type inference in action. You can skip keying in the type details for the most part, but Scala requires type declaration in a few places. We have to specify types when

  • defining class fields with no initial value

  • defining parameters for functions/methods

  • defining return type for a function/method, but only if we use an explicit return or use recursion

  • defining a variable with a different type than the value may imply—for example, val frequency: Double = 1

Other than these situations, type information is optional and, if left out, Scala will infer it. It may take some getting used to—to undo some Java practices—before you get comfortable skipping the type information. As you make the transition, no worries, Scala will patiently accept the type information, albeit redundant. Start out by mentioning types and, as you get comfortable, leave out types where you feel it’s not necessary. If Scala can’t figure out the type, it will ask for it, loud and clear.

Type Inference for Generics and Collections

Scala provides type inference and type safety for the Java generics collections as well. In the following example we define instances of ArrayList, first with explicit type and then with type inferred.

MakingUseOfTypes/Generics.scala
 
import​ java.util._
 
 
var​ list1 : ​List​[​Int​] = ​new​ ArrayList[​Int​]
 
var​ list2 = ​new​ ArrayList[​Int​]

Again, in this case, leaving out the type makes the code less noisy, especially since it’s quite obvious from the context of the declaration. Scala is quite vigilant about the types of objects. It prohibits any conversions that may cause typing issues. Here’s an example:

MakingUseOfTypes/Generics2.scala
 
import​ java.util._
 
 
var​ list1 = ​new​ ArrayList[​Int​]
 
var​ list2 = ​new​ ArrayList
 
list2 = list1 ​// Compilation Error

We created a variable, list1, that refers to an instance of ArrayList[Int]. Then we created another variable, list2, that refers to an instance of ArrayList with an unspecified parametric type—we’ll soon find out what this manifests into. When we try to assign the first reference to the second, Scala gives this compilation error:

 
Generics2.scala:5: error: type mismatch;
 
found : java.util.ArrayList[Int]
 
required: java.util.ArrayList[Nothing]
 
Note: Int >: Nothing, but Java-defined class ArrayList is invariant in
 
type E.
 
You may wish to investigate a wildcard type such as `_ >: Nothing`. (SLS
 
3.2.10)
 
list2 = list1 // Compilation Error
 
^
 
one error found

For the list2, behind the scenes, Scala actually created an instance of ArrayList[Nothing]. Nothing is a subtype of all classes in Scala. By treating the new ArrayList as an instance of ArrayList[Nothing], Scala rules out any possibility of adding an instance of any meaningful type to this collection. This is because we can’t treat an instance of base as an instance of derived and Nothing is in the bottommost place in the type hierarchy.

Scala insists the collection types on either side of an assignment are the same—we’ll see later in Variance of Parameterized Type how to alter this default behavior.

Here is an example using a collection of objects of type AnyAny is the base type of all types:

MakingUseOfTypes/Generics3.scala
 
import​ java.util._
 
 
var​ list1 = ​new​ ArrayList[​Int​]
 
var​ list2 = ​new​ ArrayList[​Any​]
 
 
var​ ref1 : ​Int​ = 1
 
var​ ref2 : ​Any​ = null
 
 
ref2 = ref1 ​//OK
 
 
list2 = list1 ​// Compilation Error

This time list1 refers to an ArrayList[Int], while list2 refers to an ArrayList[Any]. We also created two other references, ref1 of type Int and ref2 of type Any. Scala has no qualms if we assign ref1 to ref2. It’s equivalent to assigning an Integer reference to a reference of type Object in Java. However, Scala doesn’t allow, by default, assigning a collection of arbitrary type instances to a reference of a collection of Any instances—later we’ll discuss covariance, which provides exceptions to this rule. Java generics enjoy an enhanced type safety in Scala.

When dealing with generics as well, you don’t have to specify the type in order to benefit from Scala type checking. You can rely on type inference where it makes sense. The inference happens at compile time. You can be certain that the type checking takes effect right then when you compile the code, without incurring any runtime overhead.

Scala insists that a nonparameterized collection be a collection of Nothing and restricts assignment across types. These combine to enhance type safety at compile time—providing for a sensible, low-ceremony static typing.

In the previous examples, we used Java collections. Scala also provides a wealth of collections, as we’ll see in Chapter 8, Collections.

We’ve gotten a glimpse of Any and Nothing. Let’s take a closer look at these types.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.205.123