Explicit and Implicit
When types are visible in the source code, the language has explicit types. Java, C, and C++ all have explicit types because we see the types listed in variable declarations and method signatures, as demonstrated by this Java function:
public static int absoluteValue(int x) {
boolean isNegative = x < 0;
return isNegative ? -x : x;
}
A language in which types are not spelled out in the source has implicit types, as demonstrated by this Ruby function:
def absoluteValue(x)
isNegative = x < 0
isNegative ? -x : x
end
We may be tempted to extrapolate from these two examples that statically-typed languages are explicit, and dynamically-typed languages are implicit, but don't do it. Some statically-typed languages allow us to omit the type if the compiler can infer the type from the context. Java supports type inference in a few contexts. When calling a generic constructor or method, we can leave off the generic parameter if it is redundant, as in an assignment statement:
// ArrayList<String> languages = new ArrayList<String>();
ArrayList<String> languages = new ArrayList<>();
Types can also be inferred from parameters, as in this tuple instantiation:
class Tuple2<T, U>(T first, U second) {
private T first;
private U second;
public Tuple2(T first, U second) {
this.first = first;
this.second = second;
}
}
// new Tuple2<String, Integer>("GWB", 9999);
new Tuple2<>("GWB", 9999);
In Java 10, we may use the keyword var
in a local variable declaration instead of a specific type name. Some declarations benefit more than others:
// int i = 0;
var i = 0; // Doesn't save much.
// BufferedImage z = foo();
var z = foo(); // Legal but unreadable. Unhelpful names.
// Person person = new Person();
var person = new Person();
// HashMap<String, ArrayList<Integer>> concordance = new HashMap<String, ArrayList<Integer>>();
var concordance = new HashMap<String, ArrayList<Integer>>();
C++ provides similar type inference with the keyword auto
.
Type inference means programmers have less code to write and read. This brevity has costs. Redundancy is not entirely evil; it can reinforce and double-check. Removing explicit types may make code harder to understand. Even if poor names are chosen for variables or the methods called in the initialization, an explicit type at least gives some clue about the data. When the explicit type is gone, programmers might not know the shape of the data they are working with. The Java designers acknowledge this danger:
Q4. Won’t bad developers misuse this feature to write terrible code?
Yes, bad developers will write terrible code no matter what we do. Withholding a feature won’t prevent them from doing so. But, when used properly, using type inference allows developers to also write better code.
They mitigate the danger by restricting type inference to only local variables, which have a narrow scope. Parameters and instance variables, which have bigger scopes or occupy the intersection between scopes, cannot have their types inferred. Furthermore, type inference is only permitted when a variable's declaration includes an initialization. This ensures that all information used to infer the type is confined to a single statement.
Other languages use var
differently. In Kotlin, var
is used to declare a variable that can be reassigned and val
a variable that cannot be reassigned:
var p: Int = 7
p = 8
val q: Int = 7
// q = 8 <- illegal!
The Kotlin compiler infers a variable's type if we omit it from the declaration:
var p = 7
So, statically typed languages are often explicitly typed, but they can also be implicitly typed if the compiler performs type inference. We'll see further examples of static type inference in Haskell and Rust.
All the examples of dynamically typed languages have been implicitly typed. This begs the question: are there any dynamically typed languages with explicit typing? The flexibility of dynamic typing disappears if types are nailed down in the source code. However, explicit types could help interpreters detect type errors earlier and accelerate execution. Several languages, including Python, JavaScript, and TypeScript, are therefore experimenting with gradual typing. Programmers may choose to explicitly declare the types of variables, parameters, and return values. This Python script declares the type of variable i
:
i: int = "zero"
The types are incompatible, so this script will not typecheck. At present, the official Python interpreter does not check types, and the script executes just fine, with variable i
holding a string. To learn of type errors, we must run a separate tool. Tools that perform static analysis of source code to help us identify type errors, style violations, and other concerns but do not compile or interpret the code are called linters.