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?
Type-checking can be thought of as a task that attempts to evaluate the question of compatibility or type equivalence:
tsTry
functionfoo (x ) {// ... mystery code ...}//// TYPE CHECKING// -------------// Is `myValue` type-equivalent to// what `foo` wants to receive?foo (myValue )
This question can be asked at a function call - such as foo(myValue)
in the above example - as an assignment,
ts
// is the value y holds type-equivalent to what `x` allows?x = y
…a return,
ts
const myStrings = ["a"]/// ---cut---function bar(): string[] {// ...mystery code that might return early...////// TYPE CHECKING// -------------// Is `myStrings` type-equivalent to// what `bar` states it will return?return myStrings}
or 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 runtime.
TypeScript’s type system is static.
Java, C#, C++ all fit into this category. Keep in mind that inferrence 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 at runtime. JavaScript, Python, Ruby, Perl and PHP fall into this category.
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 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.
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”.
-
Among these are: generator function
↩yield
, accessor/mutator-based property conventions.