Kayıt ve Tuple türleri

Jan 25 2023
Değişmezlik yavaş yavaş Javascript'e doğru ilerliyor. Temporal API'nin değişmezliği benimsemesinden sonra, diğer değişmez türler, yani Records & Tuples, Javascript'e de geliyor.

Değişmezlik yavaş yavaş Javascript'e doğru ilerliyor. Temporal API'nin değişmezliği benimsemesinden sonra, diğer değişmez türler, yani Records & Tuples, Javascript'e de geliyor.

Unsplash'ta Lee Campbell'ın fotoğrafı

giriiş

Kayıtla başlayalım. Bir kayıt türü, son derece değişmez bir nesne benzeri yapıdır. Bir kayıt oluşturmak için, değişmez bir nesne kullanın ve önüne bir kare simgesi (#) ekleyin.

// 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 }

Bileşik ilkeller

Records & Tuples türleri, değişmez değerlere sahip bileşik ilkel öğelerdir . Yeterince adil ama bu beceriksiz tanım tam olarak ne anlama geliyor?

İlkel terimiyle başlayalım . Javascript şu ana kadar yalnızca yedi ilkel tür içeriyor :

  • sicim
  • sayı
  • büyük
  • mantıksal
  • Tanımsız
  • sembol
  • hükümsüz

// 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 yeni bir konsept değil. İşlevsel dillerin çoğu bu türlere sahiptir. Ayrıca, C#, Python ve Java gibi çok sayıda yaygın dil de yakın zamanda bunları benimsemiştir. Javascript'te bile, Immutable.JS kitaplığı ve diğerleri tarafından sağlanan değişmez veri yapıları üzerinde önceden yapılmış çalışmalar vardır .

İmmutable.JS'nin kayıtlarını yeni gelecek yerel kaydın sözdizimiyle karşılaştıralım:

//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 !!!

Lütfen beni yanlış anlamayın, Immutable.JS'nin çalışma şeklini eleştirmiyorum. Hiç de bile. Immutable.JS, Javascript'te değişmez türler kavramını yaygınlaştırmada harika bir iş çıkarıyor. Yine de, yerel değişmez türlerin alet kemerimizde olmasını sabırsızlıkla beklemenin zamanının geldiğini düşünüyorum .

Record & Tuple'in başlıca özellikleri

Şimdi yeni türlerin ana özelliklerine bakalım.

eşitlik

Eşitlik her zaman zor bir konu olmuştur. Klasik nesneleri kullanırken yalnızca referanssal eşitliğe güvenmeniz gerekir:

// 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

Tuples ile ilgili durum biraz farklı. Bu kod beklendiği gibi çalışır:

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

Değişmezlik

Değişmez yapılar, değiştirilebilir muadillerine göre birçok avantaja sahiptir. Javascript bağlamında değişmez yapılar, yanlışlıkla değişiklik yapılmasını önlememize ve kod tabanımızı daha kolay akıl yürütmemize yardımcı olur.

Değişmezliğe bir örnek:

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}

Record & Tuple ile Çalışma

Şimdiye kadar, size hazır veri grupları veya kayıtlar oluşturmak için sözdizimini gösterdim. Ancak, yeni sözdizimi daha gelişmiş kullanım durumlarını destekler.

Formayı kullanarak değiştirilmiş bir örnek oluşturun

Tanıdık bir yayılma işleci olan “…” kullanarak örneklerinizi değiştirebilirsiniz. Temporal nesneler için with yöntemine benzer .

Yayılma operatörü örneği:

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']

Dizileri demetlere ve nesneleri kayıtlara dönüştürme

Örnekleri değişmez türler ve benzerleri arasında dönüştürmek kolaydır:

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 ]

Record & Tuple'ı benimsemeli miyiz?

Tereddüt etmeden evet derdim. Argümanlarımı aşağıda özetledim.

Aşinalık

Marriane Bellotti'nin Kill with it Fire adlı kitabında bahsedilen fikir hoşuma gitti :

Yeni teknolojinin benimsenmesi, üstünlük yerine aşinalık tarafından yönlendirilir.

Marriane'nin konsepti buraya çok iyi uyuyor. Tuple & Record önerisinin yazarları, bu yeni ilkel türleri benimsememize yardımcı olmak için nesne ve dizinin anlamını taklit etmeye çalıştı. Sonuç olarak, kayıt, nesnelere veya dizilere benzer şekilde davranır. Bu nedenle, benimseme hızlı olmalıdır.

İş alanımızı modelleme

Etki alanı modellenirken değişmezlik ve karşılaştırma parlar. İş uygulaması geliştiricileri genellikle Kullanıcıları, Faturaları, Raporları ve diğer gerçek dünya nesnelerini temsil eden çok sayıda etki alanı sınıfı kullanır. Bu durumlarda, kayıtlar iyi durumda olabilir.

Ancak OOP fanatikleri dirgenlerini ele geçirmeden önce, açıklığa kavuşturmama izin verin: Etki alanı sınıfları için kayıt kullanmanın anemik bir etki alanı modeline - OOP anti-kalıpına yol açması gerektiğini biliyorum . Bununla birlikte, bir programlama dilinde alan kavramları için davranışları, üst düzey işlevler veya diğer işlevsel teknikler kullanarak tanımlamak da mümkündür. " İşlevsel alan modelleri her zaman anemik midir " veya " Anemik Alan Modeli Anti-Pattern değildir, SOLID bir tasarımdır" bölümlerinde bu fikir hakkında daha fazla bilgi edinmekten çekinmeyin . ”

Sonunda, hem klasik hem de anemik alan modellerini denedim ve ilki, katı OOP dilinde bile bana daha çok uydu.

Peki ya Tuples?

Kayıtlar, etki alanı sınıflarınızı modellemek için uygunken, demetler daha özel bir kullanıma sahiptir. Demetler, bir işlevden veya işleve birden çok değer döndürmenin veya iletmenin kolay bir yolunu sağlar. Javascript geliştiricileri, özellikle React geliştiricileri, Javascript'e yapısöküm ataması geldikten sonra demetleri çokça kullanıyor .

React'in useState kancası bunu mükemmel bir şekilde gösterdi:

// [count, setCount] is a classic tuple-like array
const [count, setCount] = useState(0)

Demetler için başka bir kullanım durumu, not alma modelidir . Şimdi, yerel demetler olmadan, işlevin bağımsız değişkenlerini JSON.stringify gibi seri hale getirmek için püf noktaları kullanmanız gerekir :

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
  }
}

Ayrıca sizi uyarmalıyım, lütfen demetleri dikkatli kullanın. Bazen kullanım durumunuz bir demet kullanmak için gayet iyi görünebilir. Nihayetinde, daha fazla kafa karışıklığı getirebilir. Örneğin, coğrafi koordinatlar:

const [lat, long] = getCoordinates()

const [long, lat] = getCoordinates()

// it works in both cases:
const {lat,long} = getCoordinates()
const {long,lat} = getCoordinates()

optimizasyon

Değişmez yapılar yalnızca daha temiz kod sağlamakla kalmaz, aynı zamanda gizli performans etkilerine de sahiptir. JS dinamik bir dil olduğundan, JS derleyicisi nesnenin bellek düzeni hakkında pek çok tahminde bulunmak zorundadır . İstediğiniz zaman yeni bir nesnenin alanını ekleyebilmeniz, işleri karmaşık hale getirir. Statik diller, nesnelerin şeklini önceden bildikleri için daha iyi optimizasyon sunar. Daha fazlasını öğrenmek için, V8'de fonksiyon optimizasyonu ile ilgili aşağıdaki makaleyi okuyun . Sabit türlerle, Javascript derleyicilerinin yazarları, statik yazmaya benzer şekilde bunları optimize edebilir.

Durum

Ocak 2023 itibariyle, Record & Tuple hala 2. aşamada, bu da yakın zamanda tarayıcılarımıza inmeyecekleri anlamına geliyor. Yine de, bu çalışan polyfill ve Babel eklentisi ile olası kullanım durumlarını keşfetmeye başlayabilirsiniz. Veya bu oyun alanında bu yeni türlerle oynayabilirsiniz .

Çözüm

Record & Tuple, Javascript'te başka bir büyük şey olacak. Kuşkusuz, ilk bakışta yeni Temporal API'den veya WebXR gibi süslü API'den daha az kullanışlıdır . Yine de işlevsel dillerden, kodlamamızı kesinlikle kolaylaştıracak başka bir popüler kavramı getiriyor.

Biz ACTUM Digital'iz ve bu parça Apollo Division Kıdemli Ön Uç Geliştiricisi Marek tarafından yazılmıştır .

Projeniz veya inisiyatifinizle ilgili yardım arıyorsanız, bize bir satır bırakın.