Pattern matching
match estrutural: literais, tipos, sequências, records, vaults, enums, alternativas e guardas.
A ideia#
O match do DataForge compara estruturas, não só valores. Um único bloco identifica o formato do dado e extrai suas partes:
action descrever(v):
match v:
point 0:
yield "zero"
point 1 or 2 or 3:
yield "pequeno"
point Integer as n when n bigger 100:
yield $"grande: {n}"
point Integer:
yield "inteiro"
point String as s:
yield $"texto de {len(s)} letras"
point [a, b]:
yield $"par ({a}, {b})"
point [primeiro, ...resto]:
yield $"lista de {len(resto) + 1}"
point Ponto(x, y):
yield $"ponto ({x}, {y})"
point {"tipo": t}:
yield $"vault do tipo {t}"
point Status.Ativo:
yield "ligado"
point _:
yield "outro"A convenção que organiza tudo#
| Escrita | Significa |
|---|---|
point n (minúscula) | captura — casa com tudo, liga a n |
point Integer (Maiúscula) | tipo — casa se for daquele tipo |
point Status.Ativo | valor — casa por igualdade |
point _ | curinga — casa com tudo, sem ligar nome |
point [a, b] | sequência, por comprimento |
point {"k": v} | mapa, parcial |
point P(x, y) | record por posição |
point P(nome := n) | record por nome |
point A or B | alternativa |
point p as tudo | apelido para o valor inteiro |
Minúscula captura, maiúscula testa o tipo. Sem essa convenção, point Integer seria ambíguo: comparar com uma variável chamada Integer ou testar o tipo?
Padrões de tipo#
point Integer as n:
yield $"inteiro {n}"Faz duas coisas de uma vez: testa que o valor é um Integer e liga n já com aquele tipo garantido. É o type narrowing do TypeScript, aqui como sintaxe de primeira classe.
Aceita todos os tipos das anotações, mais o nome de qualquer record, blueprint ou enum. Number casa com Integer ou Float; Any casa com tudo.
Padrões de sequência#
point []: # exatamente vazia
point [unico]: # exatamente um item
point [a, b]: # exatamente dois
point [primeiro, ...resto]: # um ou mais
point ["mover", direcao, n]: # literal + capturasCabeça e cauda: o padrão recursivo#
action somar(lista):
match lista:
point []:
yield 0
point [cabeca, ...cauda]:
yield cabeca + somar(cauda)Dois casos e a função está completa. É como se escreve sobre listas em Haskell, Elixir e Erlang. Para listas grandes, prefira >> distill — a recursão consome pilha.
Padrões de mapa e record#
point {"metodo": "GET", "rota": r}:
yield $"lendo {r}"
point Usuario(nome := n, idade := i) when i smaller 18:
yield $"{n} é menor"O casamento de mapa é parcial: o vault precisa ter as chaves citadas, mas pode ter outras. Essa é a escolha certa para dados de fora — um JSON de API sempre traz campos que você não usa.
Guardas com when#
action faixa(n):
match n:
point Integer as v when v smaller 0:
yield "negativo"
point 0:
yield "zero"
point Integer as v when v smaller_eq 9:
yield "unidade"
point Integer:
yield "grande"Se a guarda falha, o match continua para o próximo point. Por isso o encadeamento acima funciona sem repetir o teste de tipo.
A guarda enxerga o que o padrão ligou#
point Pedido(itens := i, total := t) when len(i) is 0 and t bigger 0:
yield "inconsistente: total sem itens"O padrão extrai; a guarda relaciona. Você acabou de expressar uma regra de negócio como um único caso do match.
Por que when e não given#
given já é o ternário (a given c otherwise b). Usar a mesma palavra numa guarda criaria ambiguidade real no parser: em point n given x, ele não saberia se given abre uma guarda ou um ternário. when resolve isso.
Três regras que evitam surpresa#
- Os
pointsão testados de cima para baixo; o primeiro que casa vence. - Ordene do específico ao geral — uma captura no topo torna tudo abaixo inalcançável.
- Mantenha um
default: a exaustividade ainda não é verificada.
match n:
point x: # captura tudo
yield "pegou tudo"
point 5: # INALCANÇÁVEL
yield "nunca chega aqui"