Slide 29 de 74

Cadeias que cobrem um tipo

Cobrindo um tipo
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
	}
	// Sem `else`, e nenhum é preciso: as três variantes são o Shape inteiro.
}

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"))
}
Cobrindo até onde os padrões descem
type Route {
	Page { segments: Str[dyn] }
	Missing
}

// Todo valor que um Result<Route, Str> pode ter é aceito abaixo, então
// a cadeia encerra a função e nada precisa vir depois dela.
func kind(r: Result<Route, Str>): Str {
	if r is Result.Ok(Route.Page([])) {
		return "uma página vazia"
	} else if r is Result.Ok(Route.Page([_, ...more])) {
		return "uma página de {len(more) + 1}"
	} else if r is Result.Ok(Route.Missing) {
		return "uma página ausente"
	} else if r is Result.Error(_) {
		return "nenhuma página"
	}
}

proc main(): void {
	echo kind(Result.Ok(Route.Page(["a", "b"])))   // uma página de 2
	echo kind(Result.Ok(Route.Missing()))          // uma página ausente
	echo kind(Result.Error("offline"))             // nenhuma página
}

Uma cadeia if/else if sem else encerra um caminho quando seus ramos aceitam todo valor do sujeito e todo ramo termina. É isso que permite escrever uma função total sobre uma união sem um else morto, e esquecer um caso é então um erro de compilação que nomeia um valor que nada aceita — `r` when it is `Result.Ok(Route.Missing)`.

O que conta para cobrir:

  • uma união é coberta pelas suas variantes e um Result por Ok e Error, até onde os padrões descem: Result.Ok(Route.Page(_)), Result.Ok(Route.Missing) e Result.Error(_) cobrem um Result<Route, Str>;
  • um vetor é coberto pelos seus tamanhos — [] e [first, ...rest] aceitam todos — e um Bool por true e false;
  • números, átomos e strings não têm cobertura finita, exceto um modelo que é um único buraco aberto, "{text}", que aceita toda string;
  • um ramo com mais coisa na condição do que o padrão, como shape is Shape.Circle(r) && r > 0, não conta para nada, já que pode não casar com todo círculo;
  • o sujeito é uma variável, ou um caminho de campos a partir de uma, como order.status; uma chamada não é, já que pode responder diferente a cada vez.

Uma cadeia que não pode ser provada como cobrindo o sujeito precisa de um último return, ou de um panic para um caso que não pode acontecer.