As programmers, we often come across a concept called type inference. To begin with let me clarify that type inference is not something unique to Scala, there are many other languages like Haskell, Rust and C# etc that have this language feature. Going by the bookish definition “Type inference refers to the automatic detection of the data type of an expression in a programming language”. Speaking in layman terms it means the language is intelligent enough to automatically deduce the type of the expression eg. String, Int, Decimal etc.
Having learned what is type inference, the next inevitable question is “Why?” The sole purpose of having type inference is to help the programmer avoid verbose typing but still maintain the compile-time type safety of a statically typed language. So speaking simply type inference is the amalgamation of the two best of worlds that is static and dynamic typing.
Having answered the “Why?” the next to follow is “What?” The type system is a language component that is responsible for type checking. Scala is a statically typed language, so there are always defined set of types and anything that doesn’t fall inside that set is classified as an invalid type and an appropriate error is thrown at compile time. Another way of answering this is computers are not intelligent enough to rectify human mistakes, and certain things are better handled by the compiler rather than relying on programmers to set them right. Tons of bugs are given birth due to these improper types.
The question that follows is “How it fits?” and “How does it make a difference?” the type system exists to ensure type safety, and the levels of strictness are what differentiates between different languages and run times. The ability to infer types automatically makes many programming tasks easier, leaving the programmer free to omit type annotations while still permitting type checking.
The final question before we dive into the Scala Type System details is “Can we classify the languages on basis of their type Systems?” The answer is YES. But this simple yes will still make you feel dizzy because the number of classification types is far too broad a range. Let me run you through the types :
- Dynamic type checking
- Static type checking
- Inferred vs Manifest
- Nominal vs Structural
- Dependant typing
- Gradual typing
- Latent typing
- Sub-structural typing
- Uniqueness typing
- Strong and weak typing
Even introducing each of them would be beyond the scope of this article, but feel free to explore them. The point of interest here lies the classification category of Scala. Scala is classified as a statically typed language with type inference. There is a strong relationship between functional programming and type inference.
Global Type Inference vs Local Type Inference
In the global type inference often Hindley-Milner algorithm is used to deduces the types. The Hindley-Milner algorithm is also called as Global type inference. It reads the source code as a whole and deduces the types. Scala’s type system works in a little different manner. Scala deduces using local type inference. Scala’s follows a combination of sub-typing and local type inference.Let me elaborate the above with an example :
def factorial(a: Int) = if (a <= 1) 1 else a * factorial(a – 1)
The correct looking code snippet shall give you the following error: Scala type errorThe above snippet computes the factorial value based on the number passed in. If we notice the error, the compiler is not able to deduce the type of the recursive function.
Surprisingly, the same (almost same) code can execute in Haskell without any errors.
let factorial 0 = 1; factorial n = n * factorial (n – 1)
The answer to it is “Haskell Global type inference”. In Scala, we have to annotate the types wherever local type inference does not help. In order for the above snippet to work in Scala, notice the type Int is explicitly mentioned.
def factorial(a:Int): Int = if(a <=1) 1 else a * factorial(a – 1)
The question that comes up is “Why did Scala use local type inference over Global type Inference” For languages that are multi-paradigm, it is really hard to do global or Hindley-Milner style type inference since it restricts implementing OOP features such as inheritance and method overloading. Languages like Haskell still do it but Scala has decided to take a different trade-off.
Scala’s Type System and Sub Typing
A type system is made up of predefined components or types and this forms the foundation of how they are inferred. If we starting digging further in Scala source code we would find that it all points to the Any class. It is worth noting that types are not regular classes, although they seem to be.
Sub-typing is not supported by the Hindley-Milner algorithm, but it is essential in a multi-paradigm world. This is also another reason why Scala does not use the HM algorithm.
Let us try to understand subtyping with help of an example when we are constructing a heterogeneous list, the sub-typing converts the lower type into a higher type wherever necessary. A simple example would be of converting an Int to a Double. If it cannot be fit, it goes to the top level i.e the Any type. All of these conversions can be translated to the type system hierarchy.
scala> List(10, ‘a’)
res0: List[Int] = List(10, 97)
res1: List[Double] = List(20.2, 10.0)
scala> List(“Hello”, 10, true)
res2: List[Any] = List(Hello, 10, true)
After having the understanding of the Scala Type system and subtyping let us understand when to use them and most importantly when to not use them. It is good to use them when it saves programmer time and also where type information does not really matter. Situations could be inside of a function or a loop where the information about types is obvious. And one should definitely avoid using them when type information is important i.e it should not leave the programmer who reads to code guessing about types. And with guessing comes mistakes, with mistakes come bad code, and with bad code comes frustration, with frustration, comes the end of all.
During the course of the blog, we have aptly discussed that a type system is a syntactic method for automatically checking the absence of certain erroneous behaviours by classifying program phrases according to the kinds of values they compute.