I am wondering, what is the top type, $\top$, in JavaScript? The diagrams at MDN make it look as if 'null' is a top type, but wikipedia's entry for top type indicate that it is Object in JS, which makes more sense. Am I misreading the MDN article?
1 Answer
JavaScript / ECMAScript does not have a static type system, so it doesn't really make sense to talk about "types". In general, "type theory" means "static type theory", and thus you will get non-sensical results when trying to apply type-theoretic concepts to dynamically-typed languages.
From the point of view of traditional type theory, ECMAScript has either no types or a single type dynamic
, i.e. ECMAScript is either untyped or uni-typed. To make matters worse, the ECMAScript Specification also uses not one but two different kinds of types in the specification.
Part of the confusion is that the term "type" is used differently by different people. In traditional Programming Language Theory, "type" means "static type" and there is no other kind of type, period. But, there are also Computer Scientists who oppose this dogmatic view. For example, in his influential paper on Typeful Programming, Luca Cardelli admits that static typing is limited and thus dynamic typing is necessary (but argues that as much as possible should be statically typed and dynamically only if there is no other choice).
This gets even more confusing in languages like Java where both the static and the dynamic types are relevant, for example, overload resolution depends on the static type but virtual method dispatch depends on the dynamic type.
Over the years, there have been several attempts at creating a static type system for ECMASCript or an ECMAScript-like language:
- ECMAScript 4 was an abandoned revision of ECMAScript. ES4 included a whole smorgasbord of features – which is ultimately why it collapsed under its own weight – including static typing. ES4 tried to be everything for everyone, it included both delegation-based and inheritance-based code re-use, both prototype-based and class-based OOP (with interfaces thrown in for good measure), both standalone constructors and constructors with are members of classes, both dynamic and static types and everything in-between, where the static typing rules can be both nominative and structural and the types can be either manifest or inferred. It had both packages and namespaces (which, to add even more confusion, are not what you think they are). And if that weren't enough, it also contained the entirety of ECMAScript for XML (E4X).
- ActionScript was the programming language for Flash. It started out as a variant of ECMASCript, over time, it added classes and static types, then it became a superset of ECMASCript 4.
- JavaScript 2.0 was Mozilla's proposed successor of JavaScript 1.5. It was turned into a superset of ECMAScript 4.
- JScript.NET was an ECMAScript-like language for the .NET platform based on JScript with static types.
- Closure is an ECMAScript compiler / transpiler / optimizer / minifier / type checker by Google.
- Flow is a static type checker and type annotation language for ECMAScript by Facebook.
- TypeScript is a superset of ECMAScript with static typing by Microsoft.
- plus dozens of academic research projects …
- Most recently, there is a proposal to add type annotations to ECMAScript proper. However, this is not a type system, its purpose is merely to make it easier to work with systems like Flow or TypeScript by essentially having ECMAScript treat type annotations as comments and ignore them.
Of these, the two most widely-used ones are Flow and TypeScript. I am most familiar with TypeScript, so I will focus on that.
If you want to understand type systems related to ECMAScript, it makes sense to study the type systems that have been designed by people much smarter than me. All of these type systems tend to be rather complex and feature-rich with many advanced and cutting-edge features, since they are designed to capture highly dynamic and reflective programming patterns of a language that was never designed to have a static type system.
TypeScript has no fewer than 7 different "special" types, 6 of which could be reasonably classified as either a bottom type or a top type. In fact, some of those types even act as both at the same time and sometimes, the behavior depends on a configuration parameter.
any
: ⊤ and ⊥unknown
: ⊤object
: ⊤ for objectsnull
: ⊥, kinda, ifstrictNullchecks = false
undefined
: ⊥, kinda, ifstrictNullchecks = false
void
: unit, sort-of but not reallynever
: uninhabited ⊥
any
is a type that is designed to, effectively, "turn off" the type system. A value of type any
can be assigned to a reference of any arbitrary type and a value of any arbitrary type can be assigned to a reference of type any
. A value of type any
can be passed as an argument for any arbitrary parameter type. A value of type any
can be used with any arbitrary operator, and you can access any arbitrary property on a value of type any
. Depending on the setting of the configuration parameter noImplicitAny
, an operation whose type the type checker cannot figure out will either raise a type error or just be typed as any
.
Since any
is assignment-compatible with everything and everything is assignment-compatible with any
, any
acts both like a top type and like a bottom type.
The main use case for any
is when you want to adopt typing for a large, previously untyped, code base or you are interacting with an untyped third-party code base that you have no control over. Usually, if you add typing to a previously untyped code base, you will be greeted with thousands, if not tens of thousands of type errors. You can quickly "fix" every type error by annotating the type as any
, and then refine those annotations over time, preventing you to get buried under a mountain of type errors without making progress on your application.
unknown
is the tamer, type-safe, cousin of any
. unknown
was introduced relatively late in the development of TypeScript. Its use is somewhat similar to any
in that operations whose type TypeScript can't figure out can be configured to have type unknown
. Also similar to any
, unknown
acts as a top type, meaning, every type is assignment-compatible with unknown
. But the opposite is not true: unlike any
, a value of type unknown
can only be assigned to unknown
and any
. Also, you cannot access any properties on unknown
.
You always have to narrow the type first (using a conditional runtime type check, type assertion, etc.), after which TypeScript's flow-sensitive type system will no longer consider the type to be unknown
.
object
is the top type for all object types, but not primitive types.
Both null
and undefined
denote the absence of a value. If strictNullChecks = false
, they behave as bottom types which are inhabited by the values also called null
and undefined
. The fact that there are two types in TypeScript is because there are two values in ECMAScript. The fact that there are two values in ECMAScript is due to hysterical raisins. According to the ECMAScript specification, undefined
is for un-initialized variables and null
is for intentionally absent values.
void
has similar semantics to void
in C, Java, or C#. It is used the same way that unit is used in impure functional languages, i.e. to denote that an expression does not evaluate to a useful value (or, in other words, that the expression is evaluated purely for its side-effects, or in even other words, that the expression is not an expression at all but a statement). However, unit typically only has a single, useless, value (hence why it is called "unit"), whereas in TypeScript, there is no void
value. OTOH, a function typed as void
is actually allowed to return any value (so, one could say that all values are void
values) but on the other other hand, one is not allowed to do anything with a value of type void
.
never
is the "impossible" bottom type, i.e. it represents a value that will never exist. For example, a function that never returns would have the type never
.
Here is a handy table with the assignment compatibility of all the special types:
any |
unknown |
object |
void |
undefined |
null |
never |
|
---|---|---|---|---|---|---|---|
any |
✓ | ✓ | ✓ | ✓ | ✓ | ✗ | |
unknown |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ | |
object |
✓ | ✓ | ✗ | ✗ | ✗ | ✗ | |
void |
✓ | ✓ | ✗ | ✗ | ✗ | ✗ | |
undefined |
✓ | ✓ | ✓✗? | ✓ | ✓✗? | ✗ | |
null |
✓ | ✓ | ✓✗? | ✓✗? | ✓✗? | ✗ | |
never |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
[TypeScript Playground Example and with strictNullChecks = false
for comparison]