Many modern languages have a feature to elegantly represent a group of related values with the data attached to each variant. Some languages use enums with associated values (Swift, Rust), others use sealed classes (Kotlin). Sometimes this feature is called algebraic data types.
In TypeScript we need to write some amount of boilerplate for this, as it can be seen in the language comparison.
The proposal is to extend enum syntax:
// new syntax
enum Shape {
Square(side: number),
Circle(radius: number)
}
The above will be desugared (expanded by the compiler) into:
const enum ShapeKind {
Square,
Circle
}
interface Square {
kind: ShapeKind.Square;
side: number;
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
type Shape = Square | Circle;
const Shape = {
Square(side: number): Square {
return {
kind: ShapeKind.Square,
side
};
},
Circle(radius: number): Circle {
return {
kind: ShapeKind.Circle,
radius
};
}
}
To be later used as usual:
const shapes: Shape[] = [Shape.Square(2), Shape.Circle(4)];
function getArea(shape: Shape) {
switch (shape.kind) {
case ShapeKind.Square:
return shape.side * shape.side;
case ShapeKind.Circle:
return Math.PI * shape.radius ** 2;
}
}
const totalArea = shapes.reduce(
(sum, shape) => sum + getArea(shape),
0
);
Full code on TypeScript playground.
I don't think this is too far from the current enum since it already acts as both type and runtime value.
I think we're supposed to use the template, right?
My suggestion meets these guidelines:
- [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [ ] This wouldn't change the runtime behavior of existing JavaScript code
- [ ] This could be implemented without emitting different JS based on the types of the expressions
- [ ] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- [ ] This feature would agree with the rest of TypeScript's Design Goals.
This definitely counts as "non-ECMAScript syntax with JavaScript output", so I doubt this will get traction. Yes, enum is already like this, but that's a legacy decision from a simpler time.
As much as I love ADTs too, we're getting out of the runtime-affecting constructs as much as possible.
We've been chatting very lightly internally about some kind of more friendly way to write a discriminated union, but it'd definitely be runtime-free. Maybe this would be cute? Might delete later
type MyUnion on 'kind' {
'circle': { radius: number },
'square': { length: number }
}
type MyUnion = DiscriminatedUnion<"kind", {
circle: { radius: number },
square: { length: number }
}>
馃洜
type DiscriminatedUnion<K extends PropertyKey, T extends object> = {
[P in keyof T]: ({ [Q in K]: P } & T[P]) extends infer U ? { [Q in keyof U]: U[Q] } : never
}[keyof T]
Most helpful comment
As much as I love ADTs too, we're getting out of the runtime-affecting constructs as much as possible.
We've been chatting very lightly internally about some kind of more friendly way to write a discriminated union, but it'd definitely be runtime-free. Maybe this would be cute? Might delete later