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.

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
}

According to the above example, Queue, Stack and Deque have different method names, but the implementation of deque methods are same as Queue and Stack method. So, in that case, we can say that there is no subtyping between Quest and Deque or Stack and Deque, but can create the inheritance relationship between them, because of code reusability. Like in the below example:

trait Dequeue extends Queue with Stack {
	def insertFront(element: E): E = {
		super[Stack].push(element)
	}
	def deleteFront(): E = {
		super[Queue].delete
	}
	def insertRear(elemenet: E): E = {
		super[Queue].insert(element)
	}
	def deleteRear(): E = {
		super[Stack].pop
	}
}

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.

Last updated