// Type inference
val date = new Date
// No type inference
val date: Date = new Date
It's even better for generics, where the version without type
inference is often absurd:
// Type inference
val lengths: List[Int] =
names.map(n => n.length).filter(l => l >= 0)
// No type inference
val lengths: List[Int] =
names.map[Int, List[Int]]((n: String) => n.length).
filter((l: Int) => l >= 0)
When would a type not be "obvious"? Let me describe two scenarios.
First, there is obvious to the reader. If the reader cannot tell what a type is, then help them out and write it down. Good code is not an exercise in swapping puzzles with your coworkers.
// Is it a string or a file name?
val logFile = settings.logFile
// Better
val logFile: File = settings.logFile
Second, there is obvious to the writer. Consider the following
example:
val output =
if (writable(settings.out))
settings.out
else
"/dev/null"
To a reader, this code is obviously producing a string. How about to
the writer? If you wrote this code, would you be sure that you wrote
it correctly? I claim no. If you are honest, you aren't sure what
settings.out is unless you go look it up. As such, you should write it
this way, in which case you might discover an error in your code:
val output: String =
if (writable(settings.out))
settings.out // ERROR: expected String, got a File
else
"/dev/null"
Languages with subtyping all have this limitation. The compiler can
tell you when an actual type fails to satisfy the requirements
of an expected type. However, if you ask it whether two types can
ever be used in the same context as each other, it will always say yes,
they could be used as type Any. ML and Haskell programmers are cackling
as they read this.
It's not just if expressions, either. Another place this issue crops up is in collection literals. Unless you tell the compiler what kind of collection you are trying to make, it will never fail to find a type for it. Consider this example:
val path = List(
"/etc/scpaths",
"/usr/local/sc/etc/paths",
settings.paths)
Are you sure that settings.paths is a string and not a file? Are you
sure nobody will change that type in the future and then see what type
check errors they get? If you aren't sure, you should write down the
type you are trying for:
val path = List[String](
"/etc/scpaths",
"/usr/local/sc/etc/paths",
settings.paths) // ERROR: expected String, got a File
Type inference is a wonderful thing, but it shouldn't be used to
create mysteries and puzzles. In code, just like in prose, strive to
say the interesting and to elide the obvious.