TypeScript
Exhaustive eval
Write an evaluation function with exhaustive check.
Exhaustive eval
The evaluation function is written with a switch covering all node types. With the assertNever helper, the default branch gives a compile error if a case is missing. This ensures all eval functions are updated when a new node is added.
function assertNever(x: never): never {
throw new Error("Unexpected: " + x);
}
function evalNode(n: Node): number | string {
switch (n.kind) {
case "number": return n.value;
case "string": return n.value;
case "add": return "(add)";
default: return assertNever(n);
}
}
Safety with assertNever
With assertNever, the default branch guarantees the switch covers all cases. When a new node type is added, the evalNode function is no longer exhaustive; n in the default branch is not never and the assertNever(n) call gives a compile error.
function assertNever(x: never): never {
throw new Error("Unexpected");
}
function evalNode(n: Node): string {
switch (n.kind) {
case "number": return String(n.value);
case "string": return n.value;
case "add": return evalNode(n.left) + evalNode(n.right);
default: return assertNever(n);
}
}
Exercise Write an exhaustive eval
Define an assertNever(x: never): never function (with throw new Error("Unexpected") inside). Define the Node discriminated union type ("number" (value: number), "string" (value: string), "add" (left: Node, right: Node)). Write an evalNode(n: Node): string function using switch (n.kind): for number return String(n.value), for string return n.value, for add return evalNode(n.left) + evalNode(n.right), for default write return assertNever(n);. Print the result via console.log(evalNode({ kind: "add", left: { kind: "number", value: 1 }, right: { kind: "string", value: "x" } })).
Show example solution
function assertNever(x: never): never {
throw new Error("Unexpected");
}
type Node =
| { kind: "number"; value: number }
| { kind: "string"; value: string }
| { kind: "add"; left: Node; right: Node };
function evalNode(n: Node): string {
switch (n.kind) {
case "number":
return String(n.value);
case "string":
return n.value;
case "add":
return evalNode(n.left) + evalNode(n.right);
default:
return assertNever(n);
}
}
console.log(evalNode({ kind: "add", left: { kind: "number", value: 1 }, right: { kind: "string", value: "x" } }));
Test yourself
-
What does assertNever provide in an exhaustive eval function?
- A runtime error
- When a new node is added, the compiler reports the missing case
- Automatic case insertion
- Faster execution
Show answer
When a new node is added, the compiler reports the missing case — With assertNever the default branch guarantees the switch covers all cases; when a new node is added, missing cases cause compile errors.
-
assertNever in default branch
function assertNever(x: never): never {
throw new Error("Unexpected");
}
function evalNode(n: Node): string {
switch (n.kind) {
case "number": return String(n.value);
case "string": return n.value;
case "add": return evalNode(n.left) + evalNode(n.right);
default:
return ____;
}
}
Show answer
function assertNever(x: never): never {
throw new Error("Unexpected");
}
function evalNode(n: Node): string {
switch (n.kind) {
case "number": return String(n.value);
case "string": return n.value;
case "add": return evalNode(n.left) + evalNode(n.right);
default:
return assertNever(n);
}
}
-
Exhaustive eval parts
- assertNever(n)
- errors if n is not never
- default branch
- remaining after all cases
- Yeni düğüm eklenince
- missing case errors at compile
- switch (n.kind)
- covers all node types
Next lesson: Integration