Ways of categorizing type systems
Now is a good time to take a step back and think about some conceptual aspects of types and type systems. How is TypeScript similar and different from Java and JavaScript?
What is type checking?
As we discussed earlier, type-checking can be thought of as a task that attempts to evaluate the question of compatibility or type equivalence.
“is type
y
equivalent to typex
? —> “does the type ofy
fit within the type ofx
?
This question can be asked at a function call
tsTry
functionfoo (x ) {// ... mystery code ...}//// TYPE CHECKING// -------------// Is `myValue` type-equivalent to// what `foo` wants to receive?foo (myValue )
or an assignment,
ts
// is the value y holds type-equivalent to what `x` allows?x = y
or a function return,
tsTry
functionbar (): string[] {if (Type 'undefined' is not assignable to type 'string[]'.2322Type 'undefined' is not assignable to type 'string[]'.Math .random () > 0.5)return // TYPE CHECKING// -------------// Is `myStrings` type-equivalent to// what `bar` states it will return?return ["a"];}
as well as in some other more exotic situations 1.
Static vs dynamic
Sorting type systems as either static or dynamic has to do with whether type-checking is performed at compile time or not.
TypeScript’s type system is static.
Java, C#, C++ all fit into this category. Keep in mind that inference can still occur in static type systems — TypeScript, Scala, and Haskell all have some form of static type checking.
Dynamic type systems perform their “type equivalence” evaluation purely at runtime. JavaScript, Python, Ruby, Perl and PHP fall into this category, although there are some great projects like Sorbet(ruby), Mypy(python) and others that bring static type-checking to these languages.
Duck typing
“Duck typing” gets its name from the “duck test”.
“If it looks like a duck, swims like a duck, and quacks like a duck, then it’s probably is a duck”.
In practice, this is very similar to structural typing, but “Duck typing” is usually used to describe dynamic type systems.
“Strong” vs. “Weak” types
These terms, while used frequently, have no agreed-upon technical definition. In the context of TypeScript it’s common for those who say “strong” to really mean “static”.
Nominal vs structural
Nominal type systems are all about names. Let’s take a look at a simple Java example:
java
public class Car {String make;String model;int make;}public class CarChecker {// takes a `Car` argument, returns a `String`public static String checkCar(Car car) { }}Car myCar = new Car();// TYPE CHECKING// -------------// Is `myCar` type-equivalent to// what `checkCar` wants as an argument?CarChecker.checkCar(myCar);
In the code above, when considering the question of type equivalence on the last line,
all that matters is whether myCar
is an instance of the class named Car
.
TypeScript’s type system is structural
Structural type systems are all about structure or shape. Let’s look at a TypeScript example:
tsTry
classCar {make : stringmodel : stringyear : numberisElectric : boolean}classTruck {make : stringmodel : stringyear : numbertowingCapacity : number}constvehicle = {make : "Honda",model : "Accord",year : 2017,}functionprintCar (car : {make : stringmodel : stringyear : number}) {console .log (`${car .make } ${car .model } (${car .year })`)}printCar (newCar ()) // FineprintCar (newTruck ()) // FineprintCar (vehicle ) // Fine
The function printCar
doesn’t care about which constructor its argument came
from, it only cares about whether it has:
- A
make
property that’s of typestring
- A
model
property that’s of typestring
- A
year
property that’s of typenumber
If the argument passed to it meets these requirements, printCar
is happy.
Revisiting our early discussion involving types as sets of values
The argument passed to printCar
has a type that could be described as…
{ all values which contain a... make property which is a string and a model property which is a string and a year property which is a number }
Since every Car
instances and all Truck
instances meet this criteria, the sets
{all possible Car instances}
and {all possible Truck instances}
are each subsets of this set.
Going back to the concept
“is type
y
equivalent to typex
? —> “does the type ofy
fit within the type ofx
?
In typescript, this is answered as
“is type
y
equivalent to typex
? —> “is the set represented byy
a subset of the set representingx
?
-
Among these are: generator function
↩yield
, accessor/mutator-based property conventions.