Pular para o conteúdo

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:

dataforge
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#

EscritaSignifica
point n (minúscula)captura — casa com tudo, liga a n
point Integer (Maiúscula)tipo — casa se for daquele tipo
point Status.Ativovalor — 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 Balternativa
point p as tudoapelido 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#

dataforge
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#

dataforge
point []:                    # exatamente vazia
point [unico]:               # exatamente um item
point [a, b]:                # exatamente dois
point [primeiro, ...resto]:  # um ou mais
point ["mover", direcao, n]: # literal + capturas

Cabeça e cauda: o padrão recursivo#

dataforge
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#

dataforge
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#

dataforge
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#

dataforge
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 point sã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.
dataforge
match n:
    point x:              # captura tudo
        yield "pegou tudo"
    point 5:              # INALCANÇÁVEL
        yield "nunca chega aqui"