3 pontos por GN⁺ 2024-07-23 | 1 comentários | Compartilhar no WhatsApp

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

 
GN⁺ 2024-07-23
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 spec de 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

    • Exemplo de código:
      if(!Whatever.TryParse<Thingy>(input, out var output)) output = some-sane-default;
      
    • Exemplo de código:
      if(!Whatever.TryParse<Thingy>(input, out var output)) throw new ApplicationException($"Not a valid Thingy: {input}");
      
    • Recomenda-se não usar o segundo caso em drivers em modo kernel
  • É 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 required do Protocol Buffers foi um grande erro

  • O ideal provavelmente é ter tanto parsing flexível e não validado quanto parsing validado