Dependent types are types that depend on the values of other types. In most programming languages this is not possible. Types are considered compile-time tools for static code analysis. Types usually prevent bugs at compile-time.
TypeScript is a special one. TypeScript extends JavaScript and adds a compilation step on top of the just-in-time compilation of JavaScript by a run-time such as NodeJS. TypeScript adds a few features to JavaScript such as
Conditional types are the alternative to “if-then-else” on the type level. They check whether or not some given type is a sub-type of another type and decide on an output. Syntactically they use a ternary question-mark operator.
type Cond = A extends B ? C : D
This means that:
Conditional types, like most types introduced by TypeScript, are types that only exist at compile-time, pre-run-time. There is a general rule of thumb. If you need to run the program first, before you know some information, you cannot model it with conditional types. Conditional types need to be provided enough information before they can function as “if-then” expressions for types.
Using the infer
keyword, you can perform a kind of pattern-matching on the type-level with conditional types.
An example is the type that replaces the type From
recursively from a type Type
with To
:
export type Replace<Type, From, To> = Type extends object
? Type extends (infer A)[]
? Replace<A, From, To>[]
: Type extends (...args: unknown[]) => unknown
? Type
: Type extends Record<string | number, unknown>
? { [K in keyof Type]: Replace<Type[K], From, To> }
: Type extends From
? To
: Type
: Type
It is important to keep in mind that arrays and functions are objects in JavaScript (and TypeScript).
In the above recursive type definition, we start by:
From
and replacing them with To
.Complicated conditional types can be hard to-read.
TypeScript’s conditional types are reminiscent of dependent typing because a type choice is made based on another type (although not on runtime values).
Dependent types are an advanced feature of programming languages, usually only found in languages like Agda or Coq. These languages are part of the family of proof assistants. In a proof assistant you can express constraints such as “an array of exactly n
elements,” with n
being a compile-time constant or even a value computed at runtime, something not possible with TypeScript’s static type system. See Agda.