This is the first article in a series that will examine, as the title suggests, Scala’s type system, and, as a secondary concern, concepts and ideas that are related to types and type systems.
The best way to emphasize theoretical notions is to give examples. Accordingly, I chose the Scala as the instrument for this endeavor.
According to the official website, Scala is a “general purpose programming language designed to express common programming patterns in a concise, elegant, and type-safe way”.
For getting the most out of the article, basic familiarity with Scala’s syntax is required. Also object-oriented concepts like inheritance, subtyping or polymorphism should not be foreign.
Computer programming theory defines a type as a means of classification identifying an abstract concept, along with additional characteristics like the operations that can be done on values of that type, the way values of that type can be stored in memory, and so on.
For example, an
Int type can be defined as a bounded set of integers, along
with the operations that can be performed on an integer, like addition,
subtraction, division, and so on. Additionally,
Ints can be described by
characteristics like memory representation (for example, 32-bit integers in some
Int, alongside other useful types, is a kind of type called a ‘data type’
and it is present by default in most of the programming languages.
Many programming languages allow the programmer to create types. Sometimes these types denote abstractions in the problem domain. And most of the times these types are aggregates of other types.
The type system
A type becomes an essential property that is used by the compiler to determine the correctness of your code. The compiler incorporates a component called ‘type checker’ that will ensure that, given a set of constraints, no violations of these constraints exist.
The set of constraints that are verified by the type checker is also called a ‘type system’.
Unsurprisingly, a type is also a constraint. A type checker essentially associates types with computed values. By examining the flow of these values, the type checker attempts to prove that no type errors can occur.
This way, instead of having errors that manifest at runtime, the
compiler will not accept any programs that violate type restrictions. In Scala,
the way to define a type is by either defining a class, an object or by using
type keyword directly.
Type parameters allow you to define methods or types in terms of another type(s). A type parameter is a type definition that’s taken in as a parameter when calling a method, constructing a type, or extending a type. It can also be said that type parameters ‘capture’ types.
It shouldn’t be too surprising to find out that type parameters are also constraints and that they can also be validated by the type checker. In Scala, type parameters are defined by using angle brackets.
In this example, a type,
Box is defined. As the name suggests, a
Box is a sort of container, therefore things can be put into and retrieved from
it. Also, this is a box that can hold many different things. We can call it
generic Box. To model this generic box, type parameters are used.
Now someone can create and use boxes that contain different things :
So what makes type parameters useful and interesting?
Type parameters become important in the context of variance.
Once two types are in a subtyping relationship, the question of variance - how
they relate to each other in view of their type parameters - becomes essential.
Concretely, considering a
Int is a subtype of
Box[Int] be used where
Box[Number] is expected or the other way around?
Variance also describes how type parameters can be altered to create conformant
(assignable) types. In type theory, given a parameterized type
T[B] if you can assign
T[A] without errors.
Variance is yet another type of constraint that can be enforced with the help of the type checker. Type variance is complementary to type parameterization and specifies constraints like invariant, covariant and contravariant to the type system.
For this part I will focus only on invariance, leaving the other two forms for the next articles.
Invariance refers to the unchanging nature of the type parameter.
Formally, if a type is invariant, then, assuming existing types
T[A] conforms to
A must be the equivalent type of
The type from the previous example,
Box, is invariant.
For a more concrete illustration, imagine a programmer developing a warehousing solution. He will create a few types and a method for packaging things in boxes.
As noted, the method call
packFurnitureIntoBox(bBox) will not be allowed by
The reason is straightforward. Imagine the
Box type was not invariant. Then an
unsuspecting programmer could try to pack furniture in book boxes, as the
In the next article I will demonstrate that it’s actually possible to relax the
invariance restriction placed upon the type
Box, I will discuss the forces
that could lead to such a venture, and of course I will also clarify things with