Slide 29 of 74

Chains that cover a type

Covering a type
type Shape {
	Circle    { radius: Int }
	Rectangle { width: Int, height: Int }
	Point
}

func area(shape: Shape): Int {
	if shape is Shape.Circle(r) {
		return 3 * r * r
	} else if shape is Shape.Rectangle(w, h) {
		return w * h
	} else if shape is Shape.Point {
		return 0
	}
	// No `else`, and none needed: the three variants are the whole of Shape.
}

func size(found: Result<Int, Str>): Int {
	if found is Result.Ok(n) {
		return n
	} else if found is Result.Error(_) {
		return -1
	}
}

proc main(): void {
	echo area(Shape.Rectangle(3, 4))
	echo size(Result.Error("missing"))
}
Covering as deep as the patterns go
type Route {
	Page { segments: Str[dyn] }
	Missing
}

// Every value a Result<Route, Str> can be is taken below, so the
// chain ends the function and nothing has to follow it.
func kind(r: Result<Route, Str>): Str {
	if r is Result.Ok(Route.Page([])) {
		return "an empty page"
	} else if r is Result.Ok(Route.Page([_, ...more])) {
		return "a page of {len(more) + 1}"
	} else if r is Result.Ok(Route.Missing) {
		return "a missing page"
	} else if r is Result.Error(_) {
		return "no page at all"
	}
}

proc main(): void {
	echo kind(Result.Ok(Route.Page(["a", "b"])))   // a page of 2
	echo kind(Result.Ok(Route.Missing()))          // a missing page
	echo kind(Result.Error("offline"))             // no page at all
}

An if/else if chain with no else ends a path when its branches take every value of their subject and every branch ends. That is what lets a total function over a union be written without a dead else, and dropping a case is then a compile error naming a value nothing takes — `r` when it is `Result.Ok(Route.Missing)`.

What counts toward covering:

  • a union is covered by its variants and a Result by Ok and Error, as deep as the patterns go: Result.Ok(Route.Page(_)), Result.Ok(Route.Missing) and Result.Error(_) cover a Result<Route, Str>;
  • a vector is covered by its lengths — [] and [first, ...rest] take every one — and a Bool by true and false;
  • numbers, atoms and strings have no finite cover, except a template that is one open hole, "{text}", which takes every string;
  • a branch with more to its condition than its pattern, like shape is Shape.Circle(r) && r > 0, counts for nothing, since it may not match every circle;
  • the subject is a variable, or a path of fields from one like order.status; a call is not, since it may answer differently each time.

A chain that cannot be proved to cover its subject needs a last return, or a panic for a case that cannot happen.