Type Systems perform an important role for removing code duplication, run-time safety for reducing program errors and more. Initially, when I was learning Java, I saw generics which help us to use the collections in a type-safe manner for avoiding runtime problems like:

// create a list with the integer value ArrayList list = new ArrayList(); list.add(1); // read the list and cast the value into the string (String) list.get(0); // Runtime Exception ClassCastException

These were the general problems which most developers faced in their day to day coding. Before generics, the List add method accepted the Object type and the get method returns the Object type, so if we really want to use some specific behavior of the added element's in the list, we'd have to cast that object manually. For resolving these problems, generics comes into the picture and can save the developer's life. I was really happy to use collections with generics like:

// add the integer to the list ArrayList<Integer> list = new ArrayList<>(); list.add(2); // read the values from the list Integer value = list.get(0);

Now things are more safe and clean and you don't have to worry about runtime exceptions. After that, I started to learn about generics and how we can create our custom classes via generics with their use-cases like the DAO layer. But when I moved to Scala, I saw a huge feature list of the Scala type system. These features are split into a chapter with small and crisp examples. We are trying to cover all of those features which are mostly used for developing Scala libraries and help us to contribute to Scala open source projects.

Chapter 1: What are the types?

Before moving to the types, let's see what are the values. Values are like raw material or anything: 2, c, “string”, 4.3 and more. In mathematical terms, we can say values are infinite. Like in the diagram below:

We have an endless circle which contains the infinite number of elements or values. Those values are anything. For recognizing those values, types come into the picture. As we know, all computer science concepts are derived from mathematics, for recognizing the values from the endless circle, based on mathematics set-theory, simple type theory comes into the picture. These theories are a further huge topic, we are not exploring those in this book. Let's break this endless circle into a small set of circles based on their associated types.

As you can see, now we have a separate set of values which are defined on the basis of their types, but still, the range of the set is based on the type only. Like integer set contains values on the basis of integer range, but it only contains integer type values.

Daniel Spiewak explains this in this one of his talk High Wizardry in the Land of Scala. In this, he explains the scope of the types and values like in this diagram:

val a: Int = 4 val b: Int = 5 val string: String = “Hello World”

In this example, we have 3 values and two types and we know, with single type integer we can create value from -2147483648 to 2147483647 range. Same with the string type, we can create an infinite number of values, but the type is still the same which is called String. So, that’s why we can say that types are less than values.

We can therefore say that type is something similar to a specific type of container which holds the value on the basis of its properties and shape. For exampe, if we were carrying water we'd require any old containers like a glass, bottle or bucket. But if we want to carry chemicals, we'd require a special safe container. So, on the basis of the values we require typing.

Chapter 2: Types Vs Classes and Subtyping Vs Inheritance

In Scala we have predefined classes and we can create our own classes as well, which are we called user-defined classes. But sometimes, we are using Class and Type words interchangeably, is it correct? Not exactly. Types and Classes are two different things and in the layman terms, we can say that Classes can be Types but not vice versa.

Types are an abstraction which contains behavior but Classes are concrete implementation, which contains the properties and behavior.

trait NewType { … } class NewClass(property: Int) { … }

Most of the Java/Scala backgrounds always have an illusion, about these two are the same. But while looking at other language(s) like Haskell and OCaml, the picture is clear. Now, we have some brief idea about that Types and Classes are different. Now, do you think SubTyping and Inheritance are same? Not exactly, the implementation of subtyping and inheritance still depends on language to language and according to mathematics, these are different. In languages like OCaml, it supports structural subtyping while in Java/Scala they support nominal subtyping.

Looking for your next great Functional Programming Job? Sign up here to get access to hundreds of jobs sorted by salary, location and benefits.

Madhavan Mukund explains SubTyping and Inheritance with a beautiful way in his lecture notes called Programming Language Concepts. He explains like this way:

Sub Typing

Subtyping concept is generally based on the principle called Liskov Substitution principle where type B is a subtype of A if every function invoked on an object type of A is also be invoked in object type of B. Languages like Java/Scala we can use inheritance as of subtype perspective. But Scala is a mixture of both OOP and FP, so we can use a bit of structural subtyping via duck typing design pattern. Madhavan Mukund explains this with the help of examples, where we are going to convert into Scala code.

trait Queue { def deleteFront(): E def insertRear(elemenet: E): E } trait Stack { def insertFront(element: E): E def deleteFront(): E } trait Dequeue { def insertFront(element: E): E def deleteFront(): E def insertRear(elemenet: E): E def deleteRear(): E }

In the above examples, we have a Queue , Stack , and Deque . The Queue contains two methods deleteFront and insertRear . The stack contains two methods insertFront and deleteFront . Dequeue contains four methods, deleteFront , insertRear , insertFront and deleteFront . In the application, if we are using Queue and Stack, we can easily replace Queue and Stack with DeQueue even there is no inheritance relationship between these classes but not vice versa. Like below code:

def performOperationOnPassedQueue(queue: Queue) = { queue.insertRear(13) queue.deleteFront() } def performOperationsOnPassedStack(stack : Stack) = { stack.insertRear(13) stack.deleteFront() } // Change Queue and Stack from Dequeue def performOperationOnPassedQueue(dequeue: Dequeue) = { dequeue.insertRear(13) dequeue.deleteFront() } def performOperationsOnPassedStack(dequeue : Dequeue) = { dequeue.insertRear(13) dequeue.deleteFront() }

In that case, we can say that Dequeue is a subtype of Queue and Stack . Because according to Liskov Substitution, if B is replaced by A then B is a SubType of A.

Inheritance

Inheritance is used in the perspective of code reusability, if class A has some properties or behaviors, with help of inheritance we can reuse those behaviors.

trait Queue { def delete(): E def insert(elemenet: E): E } trait Stack { def push(element: E): E def pop(): E } trait Dequeue { def insertFront(element: E): E def deleteFront(): E def insertRear(elemenet: E): E def deleteRear(): E }

Languages like Scala/Java, it is really difficult to separate out these concepts because via inheritance subtyping is also associated. But these concepts are actually different and used for a specific purpose.

Nominal Typing

Jamie Kyle gives a beautiful and simple example of Structural Typing vs Nominal typing within his blog. Let’s see, how can we implement Nominal and Structural subtyping in Scala.

class Foo { def method(value: Int): String = { … } } class Bar { def method(value: Int): String = { … } } val foo: Foo = new Bar // Compile time error in Scala, because there is no inheritance between these two classes and Scala/Java support nominal subtyping

Structural Subtyping

class Foo { def method(value: Int): String = { … } } class Bar { def method(value: Int): String = { … } } def structuralSubTyping(obj: { def method(value: Int): String }): String = { … } structuralSubTyping(new Foo) structuralSubTyping(new Bar)

In Scala Structural Subtyping is also called Duck Type design pattern, where we can say, if it walks like a duck, quack like a duck so, we can say that, it is a duck. As per scala best practices, we need to avoid this structural subtyping, because of internally it uses reflection which makes our code slow.

Note: In Java or Scala for achiveing subtyping we generally using inheritence. So, in this book we will be using subclass and subtype interchangeably.

Chapter 3: Type Disciplines

Whenever we talk about programming languages, there are various ways to categorize them, but the more general way for categorizing the language in depends on its behaviors. As shown in below diagram:

Dynamic and Static are two general categories for programming languages, there is endless debates are available throughout the internet where programmers are fighting about which one is good or bad. Here we are not going to conclude this discussion, everyone has its own opinion and both categories have there pros and cons. Today we are briefly walking through these categories and their further sub-categories.



So, Dynamic and Static languages are divided into further sub-categories which are Strong and Weak. These categories are represented in the form of a mathematical graph as below:

Both Dynamic and Static languages are based on the Strong and Weak type system. These concepts are beautifully explained by Heather Miller in her blog.

Dynamic

The language where types are resolved during runtime. Dynamic languages are interpreted languages where there is no compiler requires and code is directly executed by the interpreter or VM’s.

Strong Dynamic

The language which checks the correctness of the program at runtime and preventing one from calling a method from String (as an example) on that to an Int. Python is one of the examples of strong dynamic type language.

Weak Dynamic

The language provides no way for the programmer to write an arbitrary memory location. It allows the user to directly manipulate the memory like JavaScript, Assembly and more.

Static

The language where the program is executed in two phases which are compilation than execution. In this type of languages, compiler plays the role of developer guard, which help us to resolve 50% of runtime issues during compile time.

Strong Static

At compile time the language ensures that a certain value with a specified type like Int is correctly used throughout the program so that at runtime nothing else other than specified type can be held that memory location. Examples like Scala, Java and more…

Weak Static

At compile time the language ensures that a certain value with specified type is correctly used throughout the program but at runtime, you can change the value at the specified type memory allocation. Examples like C and more...

This blog from a Python site is also very useful.

Looking for your next great Functional Programming Job? Sign up here to get access to hundreds of jobs sorted by salary, location and benefits.