Médio TypeScript
Máquina de estados para um semáforo
Enunciado
Modele o estado de um semáforo como uma união discriminada com os estados 'vermelho', 'amarelo' e 'verde'. Cada estado pode ter um tempo de duração opcional. Crie uma função proximoEstado(estado: EstadoSemaforo): EstadoSemaforo que retorna o próximo estado seguindo a ordem: vermelho → verde → amarelo → vermelho, e uma função descricao(estado: EstadoSemaforo): string que retorna uma descrição legível. Use switch exaustivo com never.
Requisitos
- A união
EstadoSemaforodeve ter três variantes com o campocorcomo discriminante e um campo opcionalduracao(em segundos). - A função
proximoEstadodeve retornar o próximo estado na sequência cíclica. - A função
descricaodeve retornar uma string descritiva para cada estado. - Ambas as funções devem usar
switchexaustivo comnevernodefault.
Código inicial
type EstadoSemaforo = // complete aqui
function proximoEstado(estado: EstadoSemaforo): EstadoSemaforo {
// implemente
}
function descricao(estado: EstadoSemaforo): string {
// implemente
}
Saída esperada
Vermelho (30s) -> Verde (25s)
Verde (25s) -> Amarelo (5s)
Amarelo (5s) -> Vermelho (30s)
Descrição: Sinal vermelho, pare!
Descrição: Sinal verde, siga!
Descrição: Sinal amarelo, atenção!
Ver dica
Use o campo duracao com valor padrão se não for fornecido. Na função proximoEstado, retorne um novo objeto com a próxima cor e uma duração padrão.
Mostrar solução
type EstadoSemaforo =
| { cor: 'vermelho'; duracao?: number }
| { cor: 'verde'; duracao?: number }
| { cor: 'amarelo'; duracao?: number };
function proximoEstado(estado: EstadoSemaforo): EstadoSemaforo {
switch (estado.cor) {
case 'vermelho':
return { cor: 'verde', duracao: 25 };
case 'verde':
return { cor: 'amarelo', duracao: 5 };
case 'amarelo':
return { cor: 'vermelho', duracao: 30 };
default:
const exaustivo: never = estado;
return exaustivo;
}
}
function descricao(estado: EstadoSemaforo): string {
switch (estado.cor) {
case 'vermelho':
return 'Sinal vermelho, pare!';
case 'verde':
return 'Sinal verde, siga!';
case 'amarelo':
return 'Sinal amarelo, atenção!';
default:
const exaustivo: never = estado;
return exaustivo;
}
}
// Testando
const inicial: EstadoSemaforo = { cor: 'vermelho', duracao: 30 };
const proximo1 = proximoEstado(inicial);
const proximo2 = proximoEstado(proximo1);
const proximo3 = proximoEstado(proximo2);
console.log(`${inicial.cor} (${inicial.duracao}s) -> ${proximo1.cor} (${proximo1.duracao}s)`);
console.log(`${proximo1.cor} (${proximo1.duracao}s) -> ${proximo2.cor} (${proximo2.duracao}s)`);
console.log(`${proximo2.cor} (${proximo2.duracao}s) -> ${proximo3.cor} (${proximo3.duracao}s)`);
console.log('Descrição:', descricao({ cor: 'vermelho' }));
console.log('Descrição:', descricao({ cor: 'verde' }));
console.log('Descrição:', descricao({ cor: 'amarelo' }));
Passo a passo
- Definimos
EstadoSemaforocomo uma união de três objetos, cada um com a propriedadecor(discriminante) eduracaoopcional. - A função
proximoEstadousaswitchsobreestado.corpara determinar o próximo estado, retornando um novo objeto com a próxima cor e uma duração padrão. - No
case 'vermelho', retornamos{ cor: 'verde', duracao: 25 }, e assim por diante. - O
defaultcomnevergarante que todos os casos sejam tratados; se adicionarmos uma nova cor, o compilador reclamará. - A função
descricaotambém usaswitchpara retornar uma string descritiva para cada cor. - Testamos criando um estado inicial vermelho com duração 30, obtemos os próximos estados e imprimimos a transição.
- Imprimimos também as descrições para cada cor.
Por que funciona
A união discriminada permite que o TypeScript saiba exatamente quais propriedades existem em cada variante. O campo cor é um literal que atua como discriminante, permitindo o refinamento dentro do switch. O uso de never no default assegura que o código está completo e que futuras adições à união serão detectadas em tempo de compilação. Isso é essencial para modelar máquinas de estado de forma robusta.
Erros comuns
- Não usar
nevernodefault: se você retornar algo genérico, perde a verificação de exaustividade. Exemplo:default: return estado;não garante que todos os casos foram tratados. - Esquecer de tratar um caso: se omitir o
case 'amarelo', o TypeScript acusará erro nodefaultporqueestadonão seránever. - Usar
duracaosem verificar: como é opcional, acessarestado.duracaodiretamente pode resultar emundefined. Use um valor padrão ou verifique. - Discriminante com tipo amplo: se
corfor tipado comostring, o refinamento não funciona. Use tipos literais.
Outra forma de resolver
Poderia usar um mapa de transições, mas o switch é mais explícito e fácil de entender. Exemplo:
const proximo: Record<string, string> = { vermelho: 'verde', verde: 'amarelo', amarelo: 'vermelho' };
No entanto, isso perde a segurança de tipos e a verificação de exaustividade.
Saída esperada
Vermelho (30s) -> Verde (25s)
Verde (25s) -> Amarelo (5s)
Amarelo (5s) -> Vermelho (30s)
Descrição: Sinal vermelho, pare!
Descrição: Sinal verde, siga!
Descrição: Sinal amarelo, atenção!