Introduction to the Scala Type System
Introduction to the Scala Type System
This introduction into Scala's type system covers the benefits of local variable type inferences, how to use subtyping, and some general advice.
Join the DZone community and get the full member experience.Join For Free
Atomist automates your software deliver experience. It's how modern teams deliver modern software.
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# 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's terms, it means the language is intelligent enough to automatically deduce the type of the expression, e.g. String, Int, Decimal, etc.
Having learned what type inference is, 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 best of 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 sets 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 that 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 questions that follow are “How does it fit?” and “How does it make a difference?” The type system exists to ensure type safety, and the levels of strictness are what differentiate 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 Scala's 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 global type inference, often the Hindley-Milner algorithm is used to deduce the types by reading the source code. Scala’s type system works in a slightly different manner — it uses local type inference. Scala 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 will give you a Scala type error. The above snippet computes the factorial value based on the number passed in, but the compiler is not able to deduce the type of the recursive function.
Surprisingly, the same (well, almost same) code can execute in Haskell without any errors:
let factorial 0 = 1; factorial n = n * factorial (n – 1)
The reason is due to Haskell's 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, you'd have to write:
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 start digging further into the 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 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 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) scala> List(20.2,10) res1: List[Double] = List(20.2, 10.0) scala> List(“Hello”, 10, true) res2: List[Any] = List(Hello, 10, true)
Let's quickly touch on when to use type inferencing and subtyping and (most importantly) when to not use them. They are good to use when they save time and where type information does not really matter. Situations could arise 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 code guessing about types. With guessing comes mistakes, with mistakes come bad code, with bad code comes frustration, and frustration brings the end of all.
Published at DZone with permission of Pallavi Singh , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.