Harmeet Singh
26 Jun 2019
•
7 min read
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.
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.
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.
Madhavan Mukund explains SubTyping and Inheritance with a beautiful way in his lecture notes called Programming Language Concepts. He explains like this way:
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 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.
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
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.
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.
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.
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.
Harmeet Singh
Hi, this is Harmeet Singh. I am a programmer. I worked with JVM based languages (Java, Scala, etc.…) I always work with great code quality with design and patterns. I am an enthusiast of cutting-edge technologies. I start my career with Jakarta EE web applications development. As of today, I work with Functional Paradigm through Java, Scala, and Haskell. I also explore the Scala type system in-depth. I love to do workshops, discussions with others.
See other articles by Harmeet
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!