زبان Dart را بیشتر بشناسیم: record

در این مقاله به بهانه انتشار نسخه 3.0.0 زبان دارت (Dart)، به قابلیتی جدید در این زبان به نام record می‌پردازم که در نسخه های بعد از 3.0 دارت دردسترس قرار داره.

پیش از این، یک تابع در زبان دارت تنها می‌تونست یک مقدار برگشتی داشته باشه. در نتیجه توابعی که نیاز به برگردوندن چند مقدار داشتند یا باید اونها را در نوع داده هایی مانند لیست یا map بسته‌بندی می‌کردند یا کلاس جدیدی برای نگهداری این مقادیر تعریف می‌کردند.استفاده از ساختمان داده‌های untyped منجر به تضعیف type safety می‌شه (دارت یک زبان type safe است یعنی نوع داده‌ی یک متغیر رو به صورت ایستا و نیز در زمان اجرا چک می‌کنه تا تضمین کنه نوع داده تغییر نکرده). تعریف کلاس تنها با هدف انتقال مقدار بین توابع، سهولت و سرعت توسعه رو کاهش می‌ده. این دلایل بود که باعث شد record به زبان دارت اضافه بشه.


با استفاده از recordها می‌تونید ساختمان داده واضحی برای ذخیره چند مقدار داشته باشید. تابع زیر رو در نظر بگیرید که نام و قد رو از JSON ورودی می‌خونه و هر دو رو در یک record برمی‌گردونه:

(String, int) userInfo(Map<String, dynamic> json){
     return (json['name'] as String, json['height'] as int);
}

این syntax باید برای توسعه‌دهنده‌های دارت آشنا باشه. record شبیه یک لیست مثل ['Jack' ,'Manager'] است منتها از پرانتز به جای براکت استفاده شده. record در دارت یک قابلیت کلی هست یعنی فقط محدود به استفاده به عنوان مقادیر برگشتی تابع نیست. میتونید record رو در متغیر ذخیره کنید، داخل لیست قرار بدید، به عنوان کلید در یک Map استفاده کنید یا یک record حاوی record های دیگه‌ای ایجاد کنید. حتی می‌تونید در record فیلدهای بدون نام (مشابه چیزی که در کد بالا دیدید) یا فیلدهای دارای نام (named) تعریف کنید مثل:

(42, description: 'Meaning of life')

توجه کنید که recordها value type هستند (در مقابل reference type ها رو داریم که اشاره‌گری به مقدار متغیر در حافظه نگه می‌دارند) و شناسه ندارند. به این ترتیب کامپایلرها می‌توانند در برخی شرایط آبجکت record رو به طور کامل حذف کنند. record ها سایز ثابت دارند و ناهمگون هستند یعنی مقادیر درون record می‌تونند نوع داده‌های متفاوتی داشته باشند.

var record = ('first', a:2, b: true, 'last');

یک متغیر از نوع record رو به این‌صورت می‌تونیم تعریف و مقداردهی اولیه کنیم:

// Record type annotation in a variable declaration
(String, int) user;
// Initialize it with a record expression
user = ('Jack', 32);

دقت کنید که اگر record با فیلدهای دارای نام تعریف کردید، نام فیلدها هم بخشی از تعریف record محسوب میشه و حتی اگر نوع داده و تعداد فیلدهای دو record یکسان باشه این دو record بخاطر نام فیلدها متفاوت هستند. مثال زیر رو ببینید:

({int a, int b}) recirdAB = (a: 1, b: 2); 
({int x, int y}) recirdXY = (x: 3, y: 4); 
//Compile error! These records don't have the same type.
// recordAB = recordXY;

یک موضوع جالب دیگه در مورد record ها این هست که برای تعریف record کلمه کلیدی نداریم. recordها به صورت ساختارمندی نوع شون بر اساس نوع فیلدهاشون تعریف میشه. شکل (shape) یک record (مجموعه فیلدهاش، نوع فیلدهاش و نام فیلدها -اگر دارای نام بودند-) به صورت یکتا نوعش رو مشخص می‌کنه.


فیلدهای record رو می‌تونید از طریق متدهای getter از پیش تعریف شده بخونید. چون record ها immutable هستند برای فیلدهاشون انتظار وجود setter نداشته باشید. به مقدار فیلدهای بدون نام record به صورت <position>$ میشه دسترسی داشت. حالا مثالی ببینیم از این که چطور مقادیر فیلدهای دارای نام و بدون نام record رو با استفاده از getter بخونیم:

var record = ('first', a:2, b: true, 'last');

print(record.$1);   //Prints 'first'
print(record.a);   //Prints 2
print(record.b);   //Prints true
print(record.$2);   //Prints 'last'

عملگر == و توابع hashcode به صورت خودکار برای record تعریف شده. دوتا record برابر هستند اگر شکل یکسانی داشته باشند و فیلدهای متناظرشون مقادیر یکسان داشته باشند. چون ترتیب فیلدهای دارای نام بخشی از شکل record نیست، ترتیب شون روی تساوی تاثیر نداره.