DEV Community

Cover image for 100 primatas ou uma classe. Quem vence?
itacirgabral
itacirgabral

Posted on

100 primatas ou uma classe. Quem vence?

INTRODUÇÃO

Object Calisthenics me fizeram lembrar de vários conceitos de Orientação a Objetos e eu gostaria de aplicar os mesmos raciocínios em typescript. Não apenas portar o código para outra sintaxe, mas conseguir traduzir as necessidades e a estratégia para outro modelo de tipagem.

O terceiro exercício de calistenia é bem frutífero para análise da linguagem porque expõe restrições e recursos que divergem em fundamento da abordagem clássica. Uma definição possível, por William Durand:

3. Wrap All Primitives And Strings

Following this rule is pretty easy, you simply have to encapsulate all the primitives within objects, in order to avoid the Primitive Obsession anti-pattern.

If the variable of your primitive type has a behavior, you MUST encapsulate it.

Após o uso desta técnica continua existindo a mesma quantidade de primatas, então nem é um problemas com eles em si. Mas todas suas interações passam a ser tuteladas por um objeto maior de idade com carteira de habilitação. Um primata solto pode estar fazendo qualquer coisa - o subtítulo do produto ou não fazendo nada.

ape with machine gun

E quando ele é passado pra frente acontece uma completa amnésia de seus objetivos, preferências e sonhos, não dá mais pra discernir sua identidade no meio do rebanho, enquanto que objetos são alocados na heap e podem reter traços de história.

const ape1 = 'xita'
ape1.dream = 'banana'

const ape2 = () => 'xita'
ape2.dream = 'banana'

const showDreams = x => console.log(`My dream is ${x.dream}`)
showDreams(ape1) // My dream is undefined
showDreams(ape2) // My dream is banana
Enter fullscreen mode Exit fullscreen mode

Mesmo dois objetos criados iguais possuem referências diferentes e são tratados como indivíduos.

Tipos Marcados

Vestimos eles com nossos signos de civilização para envergonhar a natureza latente que espreita nossa própria falta de critério e excesso de controle.

Black and white photograph At Taronga Park Zoo of an ape dressing human clothes and roller skates

O que antes era produzido, regulado e consumido localmente, de forma quase banal, passa a ser governado externamente e ganha economia de escala. Isso em nome da produtividade e alto padrão de qualidade. Podemos defender o is-odd se atravessar o perímetro urbano para outro pacote for disponível. Ou até mesmo uma gentrificação continental se pudermos emigrar pra outra linguagem, segundo Greenspun's Tenth Rule:

Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp

Mas vou traçar a linha do exagero antes mesmo da classe.

Symbol em typescript é um tipo primitivo não enumerável, significa que você não o enxerga se for usado como nome de atributo. E para conseguir um novo você precisa chama uma função própria, e ela nunca os repetem.

const k = Symbol();
export const myBag = {
    [k]: 'serial number 123456'
}
export const getSerialNumber = x => x[k]
Enter fullscreen mode Exit fullscreen mode

Apenas getSerialNumber tem acesso ao símbolo de k pra conseguir ler o atributo privado. Mas ainda não é possível marcar outros tipos primitivos em tempo de execução com esses símbolos, como adicionar este atributo a uma string? Podemos fazer isso em tempo de desenvolvimento na tipagem:

declare const brand: unique symbol;
export type Brand<T, TAG> = T & { [brand]: TAG }
Enter fullscreen mode Exit fullscreen mode

A intersecção A & B é o subconjunto que satisfaz simultaneamente A e B. Em tempo de execução B como objeto praticamente vazio não macula a desenvoltura de A, mas o compilador vai conseguir distribuir e diferenciar Brand<string, 'CPF'> de Brand<string, 'EMAIL'>.

Editor mostrando erro de tipagem

Esta diferenciação não acontece com type Email = string! A tag só existe no mundo dos tipos, e mesmo se sobrasse após a compilação nem seria acionável porque estaria atrás de um Symbol.

Cop gorilla

Para centralizar o policiamento podemos criar uma função responsável pela manufatura dos tipos Email adequadamente:

export const assertEmail = (email: string) => {
    const isEmail = true // (...)
    if (isEmail) {
        return email as Email
    }
    throw new Error(`${email} is not an email`)
}
Enter fullscreen mode Exit fullscreen mode

Você tem assegurado em tempo de execução que são manufaturados corretamente sem precisar trocar sua essência de string. E durante o desenvolvimento a tipagem coordena toda a cadeia de procedimento aconselhando o algoritmo.

Editor sem erro de tipagem

Todo Email é uma string, mas nem toda string é um Email. Todo lugar que aceitava uma string vai aceitar Email, mas nenhum lugar que aceita Email vai tolerar uma string.

Shredded Ape

A calistenia é um tipo de treinamento que utiliza o peso do próprio corpo. Mas enjaular primatas em classes significa que toda interação será mediada e o código vai precisar de refatoração. Além do custo da alocação de memória ainda exige construção de consenso, o que pode desgasta e distrair a arquitetura. Em typescript dá para taguear com símbolos e receber suporte emocional do seu editor, sem necessidade de aparelhos complexos para controlar a execução.

FAZ MEU TIPO O SUFICIENTE

A maioria das linguagens canônicas de Orientação a Objetos possuem tipagem nominal, significa que mesmo que dois objetos possuam os mesmos atributos mas foram instanciados de classes com nomes diferentes eles não são intercambiáveis. Já em typescript se tiver olhos de macaco 🙈, orelha de macaco 🙉, boca de macaco 🙊, rabo de macaco 🐒, então é um macaco 🐵. E se tiver bico de pato 🦆, orelha de porco 🐷, chifre de vaca 🐄 e só precisar de 2 olhos, serve também.

A intersecção A & B poderia ser esquematizada com interfaces assim:

interface A {
    ka1: string;
    ka2: string;
}
interface B {
    kb1: string;
    kb2: string;
}
interface A_AND_B {
    ka1: string;
    ka2: string;
    kb1: string;
    kb2: string;
}
Enter fullscreen mode Exit fullscreen mode

Se uma função necessitar parcialmente de um tipo entregue também está adequado, não precisa ser estritamente igual, basta ser suficientemente boa:

const takesA = (arg: A) => {
  //
}
const takesB = (arg: B) => {
  //
}
const givesAB = () => ({
    ka1: 'va1',
    ka2: 'va2',
    kb1: 'vb1',
    kb2: 'vb2',
})

takesA(givesAB())
takesB(givesAB())
Enter fullscreen mode Exit fullscreen mode

Repare que a função givesAB nem retorna um objeto do tipo A_AND_B, é de uma interface anônima mas que possui todos os atributos obrigatórios para cada uma das takes. E internamente as funções takes não sabem da existência de nada além do que está declarado na sua interface.

Também se aplica a uma coleção de tipos interseccionados


declare const brandEyes: unique symbol;
type BrandEyes<T, TBrand> = T & { [brandEyes]: TBrand }

declare const brandEars: unique symbol;
type BrandEars<T, TBrand> = T & { [brandEars]: TBrand }

declare const brandMouth: unique symbol;
type BrandMouth<T, TBrand> = T & { [brandMouth]: TBrand }

declare const brandTail: unique symbol;
type BrandTail<T, TBrand> = T & { [brandTail]: TBrand }

type EyesMacaco<T> = BrandEyes<T, 'MACACO'>
type EarsMacaco<T> = BrandEars<T, 'MACACO'>
type MouthMacaco<T> = BrandMouth<T, 'MACACO'>
type TailMacaco<T> = BrandTail<T, 'MACACO'>

// coleção de tipos interseccionados
type Ape = TailMacaco<MouthMacaco<EarsMacaco<EyesMacaco<string>>>>
const macaco = 'xita' as Ape

const apetician = (ape: EyesMacaco<string>) => {
    // does exams
}
apetician(macaco)
Enter fullscreen mode Exit fullscreen mode

apetician requisita como argumento apenas olhos de macaco, mas se você entregar o macaco inteiro está satisfeito o mínimo exigido.

monkey wearing glasses

Esse na foto é Liskov percebendo que não precisa de uma intrincada correlação de herança pra fazer substituições. Todos os parâmetro são como interfaces abstratas porque você não tem restrição nominal. Não é necessário criar camadas de indireção para contornar condições que nunca foram estabelecidas.

Conclusão

Tipagem estrutural é bem flexível, ainda mais contando com o turing-completo do typescript. É possível adicionar uma pitada de tempero nominal pra conseguir uma rastreabilidade mais precisa e com isto não deixar os primatas tão precarizados. Um uso comum desta técnica de branding é quando precisa classificar ids ou hashes que seriam indistinguíveis.

Aqui algumas sugestões de leitura:

monkey swinging

Top comments (1)

Collapse
 
martinsmiguel profile image
Miguel Martins

👏👏👏