A linguagem
case com guardas e padrões
when, ligação (var N), is T(var X) e o case-expressão exaustivo.
O que é e por que existe#
O case clássico só compara rótulos. Com as extensões abaixo ele deixa de precisar de uma cadeia de if quando a escolha depende de uma condição extra, do tipo do objeto ou do valor casado. Sem nenhuma extensão, o case é o de sempre (e continua virando switch no código gerado).
case X of
1..5 when APositive: ... // guarda: se for falsa, segue para o PRÓXIMO braço
7, 9 (var N) when N > 8: ... // (var N) liga o valor casado a N
else ... end;
case AShape of // seletor objeto ou interface
is TSquare(var Q) when Q.Side > 10: ... // Q já vem tipado como TSquare
is TCircle(var C): ...
end;
Result := case AColor of // case como expressão
cRed when AStrong: 3;
cRed: 1;
cGreen: 2;
cBlue: 4; // todos os membros cobertos: não precisa de else
end case;Regras#
- O seletor é avaliado uma vez; os braços são tentados na ordem escrita e a primeira guarda que passa ganha.
whené palavra contextual: só vale depois dos rótulos do braço.(var N)vem antes dowhen, só em seletor ordinal, e não vale em braço com vários rótulos (de qual seria o valor?).is T(var F):Fé uma referência fraca (emprestada, como oEdeon E: TFoo do). Quem segura o objeto é o temporário docase; não guardeFalém do braço.- Case-expressão: aceita
whenpor braço. Com seletor de enum e braços sem guarda que cobrem todos os membros, oelseé dispensado. Se o último braço tem guarda, oelsevolta a ser obrigatório. - Também aceita padrões de record e de tupla:
TPonto(X: 0, Y: var Py),(5, var S); veja Tipos soma: choice para os padrões aninhados.
program Match;
// Demo: `case` com guardas (`when`), ligação (`(var N)`), padrão de tipo (`is T(var X)`)
// e `case` como expressão (exaustivo, sem `else` quando cobre todo o enum).
type
TColor = (cRed, cGreen, cBlue);
TShape = class(TObject)
end class;
TSquare = class(TShape)
public
Side: Int32;
end class;
TCircle = class(TShape)
public
Radius: Int32;
end class;
// A guarda `when` é testada depois de o rótulo casar. Se for falsa, o case segue para o PRÓXIMO braço.
function Classify(X: Int32; APositive: Bool8): UString;
begin
case X of
1..5 when APositive: Result := 'faixa com flag';
1..5: Result := 'faixa simples'; // pega o que a guarda recusou
7, 9 (var N) when N > 8: Result := 'grande ' + IntToUStr(N); // (var N) liga o valor casado
7: Result := 'sete';
else
Result := 'outro';
end case;
end function;
// `is TSquare(var Q)` testa o tipo E já entrega a variável convertida.
function Describe(AShape: TShape): UString;
begin
case AShape of
is TSquare(var Q) when Q.Side > 10: Result := 'quadrado grande';
is TSquare(var Q): Result := 'quadrado de lado ' + IntToUStr(Q.Side);
is TCircle(var C): Result := 'círculo de raio ' + IntToUStr(C.Radius);
else
Result := 'forma genérica';
end case;
end function;
// Como expressão, o case devolve valor; sem `else` o compilador exige cobrir todo o enum.
function Weight(AColor: TColor; AStrong: Bool8): Int32;
begin
Result := case AColor of
cRed when AStrong: 3;
cRed: 1;
cGreen: 2;
cBlue: 4;
end case;
end function;
var
LSquare: TSquare;
LCircle: TCircle;
begin
WriteLn(Classify(3, True), '; ', Classify(3, False), '; ', Classify(9, True),
'; ', Classify(7, True), '; ', Classify(50, True));
LSquare := TSquare.Create;
LSquare.Side := 12;
WriteLn(Describe(LSquare));
LSquare.Side := 4;
WriteLn(Describe(LSquare));
LCircle := TCircle.Create;
LCircle.Radius := 5;
WriteLn(Describe(LCircle), '; ', Describe(TShape.Create));
WriteLn(Weight(cRed, True), ' ', Weight(cRed, False), ' ', Weight(cGreen, False), ' ', Weight(cBlue, False));
end.faixa com flag; faixa simples; grande 9; sete; outro
quadrado grande
quadrado de lado 4
círculo de raio 5; forma genérica
3 1 2 4Demo 42. Guarda, ligação, is T(var X) com guarda e o case-expressão sobre enum.
Armadilhas e erros típicos#
- Case-expressão que não cobre o enum é erro, não aviso:
FE0804("o case que dá um VALOR sem else precisa cobrir todos os membros do enum, e falta cBlue"). Um case que dá valor não pode "seguir" sem resultado. - Depois dessa mensagem boa pode vir um erro em cascata (
FE0218/FE1254: o "end case" fecha um case, mas o que está aberto é uma function). Corrija o primeiro e o resto some. - Com guarda ou ligação, o
casedeixa de ser umswitchda LLVM e perde a checagem de rótulo duplicado. - A exaustividade rastreia até 256 rótulos por case-expressão; acima disso exige
else. - No case-expressão, o seletor não pode ter efeito colateral.
- O aviso opt-in
CaseNotExhaustive(Avisos opt-in) acusa ocasede comando sobre enum semelseque esquece membros.
Veja também#
- Declarações e comandos para o
casebásico; demo05-if-case. - Demo
42-case-padroes.