Faça parsing, não valide
A essência do design orientado por tipos
- Um slogan simples para explicar design orientado por tipos (type-driven design): faça parsing, não valide
- Esse slogan significa usar o sistema de tipos para aumentar a segurança e a precisão do código
O domínio das possibilidades
- Um sistema de tipos estático permite julgar com facilidade se uma determinada função pode ou não ser implementada
- Exemplo:
foo :: Integer -> Void não pode ser implementada (Void não pode ter valores)
- Exemplo: a função
head :: [a] -> a não é definida quando a lista está vazia
Transformando funções parciais em funções totais
Gerenciando expectativas
- Como a função
head não pode retornar um valor quando a lista está vazia, pode-se usar o tipo Maybe para permitir o retorno de Nothing
- Porém, isso pode causar inconveniência no uso
Transmitindo expectativas
- Ao usar o tipo
NonEmpty para representar uma lista não vazia, é possível garantir que a função head sempre retorne um valor
- Com o tipo
NonEmpty, checks desnecessários podem ser removidos, e erros podem ser capturados em tempo de compilação pelo sistema de tipos
O poder do parsing
- A diferença entre parsing e validação está em como a informação é preservada
- A função
validateNonEmpty verifica se uma lista não está vazia, mas não preserva essa informação
- A função
parseNonEmpty verifica se uma lista não está vazia e preserva essa informação usando o tipo NonEmpty
Os riscos da validação
- Uma abordagem baseada em validação pode levar a um problema chamado "shotgun parsing"
- Isso pode criar situações em que o programa processa parte da entrada e só depois descobre que o restante da entrada é inválido
- O parsing divide o programa em duas etapas: na primeira, a validade da entrada é confirmada; na segunda, apenas entradas válidas são processadas
Parsing na prática
- Foque nos tipos de dados e torne as assinaturas de tipo das funções o mais específicas possível
- Use estruturas de dados que não consigam representar estados inválidos e converta os dados para representações concretas o mais cedo possível
- Deixe os tipos de dados guiarem o código, em vez de deixar o código controlar os tipos de dados
- Funções que retornam
m () devem ser usadas com cuidado
- Não tenha medo de fazer parsing dos dados em várias etapas
- Evite representações desnormalizadas dos dados e, quando necessário, gerencie isso por meio de encapsulamento
- Use tipos de dados abstratos que façam validadores parecerem parsers
Resumo, reflexões e leituras relacionadas
- Aproveitar ao máximo o sistema de tipos de Haskell não é difícil, e não há necessidade de usar extensões modernas da linguagem
- A ideia central é "escrever funções totais", o que é simples, mas pode ser difícil de colocar em prática
- Como leituras relacionadas, são recomendados o post de blog de Matt Parson "Type Safety Back and Forth" e o artigo de Matt Noonan "Ghosts of Departed Proofs"
Resumo do GN⁺
- Este texto explica como usar o sistema de tipos de Haskell para aumentar a segurança e a precisão do código
- Destaca a importância de entender a diferença entre parsing e validação e de confirmar a validade da entrada por meio do parsing
- É importante usar estruturas de dados que não consigam representar estados inválidos e converter os dados para representações concretas o mais cedo possível com a ajuda do sistema de tipos
- Como leituras relacionadas, são recomendados o post de blog de Matt Parson e o artigo de Matt Noonan
1 comentários
Comentários no Hacker News
Este conselho e o artigo são muito úteis
Também é útil para quem não usa linguagens funcionais com tipagem estática
Essa ideia transcende paradigmas
Conceitos semelhantes também podem ser encontrados na literatura de orientação a objetos dos anos 80 e 90, por exemplo, Design by Contract
TypeScript costuma ser escrito de uma forma que refina tipos em tempo de execução
Design by Contract provavelmente influenciou o
specde Clojure (Clojure é uma linguagem dinâmica)Basicamente, trata-se de suposições e garantias (requisitos e entregas)
Quando as suposições são verificadas e as garantias são estabelecidas, não há necessidade de verificar novamente suposições duplicadas em outras partes do programa
Pode ser confuso ver no código propriedades que já foram garantidas sendo verificadas de novo, o que dificulta entender e melhorar o código
Esse padrão também funciona bem em C# moderno e ainda economiza espaço
É bom usar um sistema de tipos forte para tornar impossível expressar casos de erro, o que ajuda a reduzir bugs de software
Leva mais tempo para pensar no problema e seguir o design, mas em muitos casos esse tempo vale a pena
O slogan "Parse, don’t validate" resume bem o design orientado por tipos
Pessoalmente, acho melhor "sempre fazer a validação em um único construtor", porque assim objetos inválidos simplesmente não existem
Para modificar um objeto, isso deve ser implementado chamando novamente o mesmo construtor para montar o novo estado
Isso lembra a seção 5 do qmail, que inclui "não faça parsing" e "há boas interfaces e interfaces de usuário"
Se eu ensinasse uma disciplina intermediária de programação, pediria aos alunos que escrevessem uma redação comparando e contrastando essas propostas; cada uma tem algo a ensinar, e no início podem parecer contraditórias
Material relacionado: Richard Feldman, "Making Impossible States Impossible"
Discussões anteriores:
Encaminhado para a Crowdstrike
Isso lembra um comentário de alguém na época da febre do XML em meados dos anos 2000: muitas organizações escolheram XML porque XML lhes dava um parser
Apesar de escrever parsers não ser difícil e ser divertido, não consigo entender por que as pessoas não querem escrevê-los
Fico me perguntando se isso contradiz a opinião de que a palavra-chave
requireddo Protocol Buffers foi um grande erroO ideal provavelmente é ter tanto parsing flexível e não validado quanto parsing validado