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 EstadoSemaforo deve ter três variantes com o campo cor como discriminante e um campo opcional duracao (em segundos).
  • A função proximoEstado deve retornar o próximo estado na sequência cíclica.
  • A função descricao deve retornar uma string descritiva para cada estado.
  • Ambas as funções devem usar switch exaustivo com never no default.

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

  1. Definimos EstadoSemaforo como uma união de três objetos, cada um com a propriedade cor (discriminante) e duracao opcional.
  2. A função proximoEstado usa switch sobre estado.cor para determinar o próximo estado, retornando um novo objeto com a próxima cor e uma duração padrão.
  3. No case 'vermelho', retornamos { cor: 'verde', duracao: 25 }, e assim por diante.
  4. O default com never garante que todos os casos sejam tratados; se adicionarmos uma nova cor, o compilador reclamará.
  5. A função descricao também usa switch para retornar uma string descritiva para cada cor.
  6. Testamos criando um estado inicial vermelho com duração 30, obtemos os próximos estados e imprimimos a transição.
  7. 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 never no default: 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 no default porque estado não será never.
  • Usar duracao sem verificar: como é opcional, acessar estado.duracao diretamente pode resultar em undefined. Use um valor padrão ou verifique.
  • Discriminante com tipo amplo: se cor for tipado como string, 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!