Tipos de registro e tupla
A imutabilidade está gradualmente chegando ao Javascript. Depois que a API Temporal adotou a imutabilidade, outros tipos imutáveis, como Registros e Tuplas, também estão chegando ao Javascript.
Introdução
Vamos começar com o registro. Um tipo de registro é uma estrutura semelhante a um objeto profundamente imutável. Para criar um registro, use um literal de objeto e o prefixe com um símbolo de cerquilha (#).
// a classic object
const user = { name: "S. Holmes", age: 27 }
const address = { street: "221B Baker Street", city: 'London'}
// a record
const user = #{ name: "S. Holmes", age: 27 }
const address = #{ street: "221B Baker Street", city: 'London'}
// array
const enemies = ["James Moriarty", "Irene Adler"]
const friends = ["G. Lestrade", "John Hamish Watson"]
// tuple
const enemies = #[ "James Moriarty", "Irene Adler"]
const friends = #["G. Lestrade", "John Hamish Watson"]
#[10, 25] can be viewed as #{0: 10, 1: 25 }
Primitivas compostas
Os tipos Records & Tuples são compostos primitivos com valores imutáveis . É justo, mas o que exatamente essa definição desajeitada significa?
Vamos começar com o termo primitivos . Até agora, o Javascript contém apenas sete tipos primitivos :
- corda
- número
- bigint
- boleano
- indefinido
- símbolo
- nulo
// primitive but not compound
const str = "Primitive value"
const number = 1
const bool = true
const n = null
// the compound value:
// consists of two primitive values
const object = { name: 'Primitive value', number: 33 }
// consists of two primitive values & one function
const object = { name: 'Primitive value', number: 33, fn: () => {} }
// record with a nested record
const record = #{ name: 'Primitive value', number: 33,
nestedRecord : #{ number: 50} }
// tuple with a nested tuple and record
const tuple = #[ 'Primitive value', 33, #{ number: 50}, #[1, true, null] ]
// FORBIDDEN usage:
// record with a nested object
// it throws an error as it’s not a primitive value anymore
const record = #{ name: 'Primitive value', number: 33,
nestedObject: { number: 50} }
Record & Tuple não é um conceito novo. A maioria das linguagens funcionais tem esses tipos de tipos. Além disso, várias linguagens convencionais, como C#, Python e Java, também as adotaram recentemente. Mesmo em Javascript, houve trabalho anterior feito em estruturas de dados imutáveis fornecidas pela biblioteca Immutable.JS e outros.
Vamos comparar os registros immutable.JS com a sintaxe do novo registro nativo:
//Immutable.JS
const record1 = Record({ a: 1, b: 2 })
const record2 = record1.set('b', 50)
const b = record2.get('b')
const recordA = Record({ a: 1, b: 2 })
const recordB = Record({ a: 1, b: 2 })
recordA.equals(recordB) // true
recordA == (recordB) // false !!!
// Native records
const recordNative = #{ a: 1, b: 2 }
const recordNative2 = #{ ...recordNative, b: 50 }
const b = recordNative2.b
const recordA = #{ a: 1, b: 2 }
const recordB = #{ a: 1, b: 2 }
recordA == (recordB) // true !!!
Por favor, não me interpretem mal, não estou criticando a maneira de trabalhar do Immutable.JS. De jeito nenhum. Immutable.JS tem feito um ótimo trabalho em popularizar o conceito de tipos imutáveis em Javascript. Ainda assim, acho que é hora de esperar ter os tipos imutáveis nativos em nosso cinto de ferramentas .
Principais recursos do Record & Tuple
Agora vamos ver as principais características dos novos tipos.
Igualdade
A igualdade sempre foi uma questão complicada. Usando objetos clássicos, você deve confiar apenas na igualdade referencial:
// Identity equality
const user = { name: 'Dr. Watson'}
const userRef = user
userRef == user // => true
const user = { name: 'Dr. Watson'}
const user2 = { name: 'Dr. Watson'}
user2 == user // => false
// Value equality
const user = #{ name: 'Dr. Watson'}
const user2 = #{ name: 'Dr. Watson'}
user2 == user // => true
const user1 = #{ name: 'S. Holmes', age: 27 }
const user2 = #{ age: 27, name: 'S. Holmes'}
user1 == user2 // => true
A situação em relação às tuplas é um pouco diferente. Este código funciona como esperado:
const friends1 = #['G. Lestrade', 'John Hamish Watson']
const friends2 = #['G. Lestrade', 'John Hamish Watson']
friends1 == friends2 // => true
const friends1 = #['G. Lestrade', 'John Hamish Watson']
const friends2 = #['John Hamish Watson', 'G. Lestrade']
friends1 == friends2 // => false
Imutabilidade
Estruturas imutáveis têm muitos benefícios sobre suas contrapartes mutáveis. No contexto do Javascript, as estruturas imutáveis nos ajudam a evitar modificações acidentais e tornam nossa base de código mais fácil de raciocinar.
Um exemplo de imutabilidade:
const primitive = 'string is an immutable primitive'
const primitive2 = primitive + '— change it'
primitive == primitive2 // => false
const obj1 = { id: 'A', value: 1}
const obj2 = obj1
// the following code changes the value of obj2.value
// but it also change obj1.value
obj2.value = 2
obj1 === obj2 // => still true
obj1.value === obj2.value // => true
// But with immutability, you can't change the record like a normal object.
const record1 = #{ id: 'A', value: 1}
record1.value = 2 // throws error
// You need to be more explicit to get changed value
const record2 = #{ ... record1, value: 2}
Trabalhando com registro e tupla
Até agora, mostrei a sintaxe para criar tuplas ou registros literais. No entanto, a nova sintaxe oferece suporte a casos de uso mais avançados.
Crie uma instância modificada usando spread
Você pode modificar suas instâncias usando um conhecido operador de propagação “…”. É semelhante ao método with para objetos Temporais.
Exemplo de operador de spread:
const user = #{ name: 'S. Holmes', age: 27 }
// with immutable types, you are restricted to changing value directly:
user.age = 28 // throws error
// With spread operator, you can copy a record
// and modify it at the same time:
const userOneYearOlder = #{ ...user, age: 28}
// => #{ name: 'S. Holmes', get: 28 }
// Of course, it also means:
user == userOneYearOlder // => false
// Tuple
const friends1 = #['G. Lestrade', 'Dr. Watson']
const friends2 = #[...friends1, 'Mrs Hudson']
// => #['G. Lestrade', 'Dr. Watson', 'Mrs Hudson']
// You can modify an item at a particular position with the method 'with'
const friends3 = friends1.with(0, 'Mrs Hudson')
// => #['Mrs Hudson', 'Dr. Watson']
const friends1 = #['Dr. Watson', 'G. Lestrade']
const friends2 = friends1.toReversed()
// => ['G. Lestrade', 'Dr. Watson']
const friends3 = #['G. Lestrade', 'Dr. Watson']
const friends4 = friends3.toSorted()
// friends4 == #['Dr. Watson', 'G. Lestrade']
Convertendo arrays em tuplas e objetos em registros
É fácil converter instâncias entre tipos imutáveis e suas contrapartes:
const record = #{ name: 'S. Holmes', age: 27 }
// Note that any iterable of entries will work
const record2 = Record.fromEntries([['age', 27], #['name', 'S. Holmes']])
const tuple = Tuple(...[1, 2, 3])
// Note that an iterable will work as well
const tuple2 = Tuple.from([1, 2, 3])
record == #{ name: 'S. Holmes', age: 27 } // => true
tuple == #[1, 2, 3] // => true
// JS Compiler throws an error when using a non-primitive value
// TypeError: cannot use an object as a value in a record
Record({ a: {} })
// TypeError: cannot use an object as a value in a record
Tuple.from([{}, {} , {}])
const obj = { ...record }
const arr = [ ...tuple ]
Devemos adotar Record & Tuple?
Eu diria que sim, sem hesitar. Resumi meus argumentos abaixo.
Familiaridade
Gosto da ideia do livro Mate com Fogo de Marriane Bellotti:
A adoção de novas tecnologias é impulsionada pela familiaridade sobre a superioridade.
O conceito de Marriane se encaixa perfeitamente aqui. Os autores da proposta Tuple & Record tentaram imitar a semântica de objeto e array para nos ajudar a adotar esses novos tipos primitivos. Como resultado, o registro se comporta de maneira semelhante a objetos ou matrizes. Assim, a adoção deve ser rápida.
Modelando nosso domínio de negócios
A imutabilidade e a comparação brilham ao modelar o domínio. Os desenvolvedores de aplicativos de negócios geralmente empregam várias classes de domínio que representam usuários, faturas, relatórios e outros objetos do mundo real. Nesses casos, os registros podem ser úteis.
Mas antes que os fanáticos por OOP peguem seus forcados, deixe-me esclarecer: eu sei que o uso de registros para classes de domínio deve levar a um modelo de domínio anêmico — antipadrão OOP. No entanto, também é possível definir comportamentos para conceitos de domínio em uma linguagem de programação usando funções de ordem superior ou outras técnicas funcionais. Sinta-se à vontade para ler mais sobre essa ideia em “ Os modelos de domínio funcional são sempre anêmicos ” ou “ O modelo de domínio anêmico não é um antipadrão, é um design SÓLIDO. ”
Por fim, experimentei os modelos de domínio clássico e anêmico, e o primeiro me serviu melhor, mesmo em linguagem OOP estrita.
E as Tuplas?
Enquanto os registros são adequados para modelar suas classes de domínio, as tuplas têm um uso mais especializado. As tuplas fornecem uma maneira fácil de retornar ou passar vários valores de ou para uma função. Os desenvolvedores Javascript, especialmente os desenvolvedores React, têm usado muito as tuplas depois que a atribuição de desconstrução chegou ao Javascript.
O gancho useState do React ilustrou isso perfeitamente:
// [count, setCount] is a classic tuple-like array
const [count, setCount] = useState(0)
Outro caso de uso para tuplas é o padrão de memorização . Agora, sem tuplas nativas, você precisa usar truques para serializar os argumentos da função, como JSON.stringify :
const memoized = new Map()
function compute(...args) {
// a hack
const key = JSON.stringify({...args})
if (memoized.has(key))
{
return memoized.get(key)
}
else {
const value = peformSlowComputation(...args)
memoized[key] = value
return value
}
}
const memoized = new Map()
function compute(... args) {
// a nice and clean key
const key = #[...args]
if (memoized.has(key ))
{
return memoized.get(key)
}
else {
const value = performSlowComputation(...args)
memoized[key] = value
return value
}
}
Devo também avisá-lo, por favor, use tuplas com cuidado. Às vezes, seu caso de uso pode parecer perfeito para usar uma tupla. Em última análise, porém, pode trazer mais confusão. Por exemplo, coordenadas geográficas:
const [lat, long] = getCoordinates()
const [long, lat] = getCoordinates()
// it works in both cases:
const {lat,long} = getCoordinates()
const {long,lat} = getCoordinates()
Otimização
As estruturas imutáveis não apenas fornecem um código mais limpo, mas também têm efeitos de desempenho ocultos. Como JS é uma linguagem dinâmica, o compilador JS precisa fazer muitas suposições sobre o layout da memória do objeto. O fato de poder adicionar um novo campo de objeto a qualquer momento complica as coisas. Linguagens estáticas oferecem melhor otimização porque conhecem antecipadamente a forma dos objetos. Para saber mais, leia o artigo a seguir sobre otimização de funções no V8 . Com tipos imutáveis, os autores de compiladores Javascript podem otimizá-los, de forma semelhante à digitação estática.
Status
Quanto a janeiro de 2023, o Record & Tuple ainda está no estágio 2, o que significa que não chegará tão cedo em nossos navegadores. No entanto, com este plug-in polyfill e Babel funcional , você pode começar a explorar possíveis casos de uso. Ou você pode brincar com esses novos tipos neste playground .
Conclusão
Record & Tuple será outra grande coisa em Javascript. Reconhecidamente, é menos útil à primeira vista do que a nova API Temporal ou API sofisticada como WebXR . Ainda assim, traz outro conceito popular das linguagens funcionais, que com certeza facilitará nossa codificação.
Somos a ACTUM Digital e esta peça foi escrita por Marek , Desenvolvedor Front-End Sênior da Apollo Division.
Se você procura ajuda com seu projeto ou iniciativa, basta nos enviar uma mensagem.





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)