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

در ادامه‌ی پست های آشنایی با قابلیت‌های جدید زبان دارت، در این پست به بررسی الگوها (Patterns) می‌پردازم. الگوها در نسخه‌های بعد از دارت 3 در دسترس هستند.

همونطور که از نامش پیداست، هدف این هست که تطبیق (match) الگویی از مقادیر رو با یک سری مقدار واقعی بررسی کنه. الگو قراره چه کاری انجام بده؟ به طور کلی، یک الگو بسته به شکلش (shape) ممکنه یک مقدار رو تطبیق بده یا یک مقدار رو تخریب کنه (destructure: یعنی ساختار یک مقدار رو از بین ببره) و یا هر دو کار رو انجام بده. برای شروع شاید این تعریف کوتاه واضح نباشه. در ادامه بیشتر در موردش توضیح میدم.

بریم سراغ بخش اول تعریف بالا. تطبیق الگو این امکان رو به شما میده که:

  • چک کنید آیا یک مقدار یک شکل مشخص داره یا نه
  • برابریش با یک مقدار ثابت مشخص رو چک کنید
  • برابریش با مقدار دیگه‌ای رو چک کنید
  • نوع داده اش رو چک کنید

حالا تخریب الگو چیه؟ تخریب الگو با یک syntax مناسب این امکان رو برای شما فراهم می‌کنه که مقداری رو به قسمت‌های تشکیل دهنده‌اش بشکنید.

تطبیق

الگو همیشه یک مقدار رو تست میکنه تا تشخیص بده که آیا این مقدار اون چیزی که شما انتظار دارید هست یا نه. به عبارت دیگه دارید تطبیق یک مقدار رو با یک الگوی مشخص چک می‌کنید. این که الگو قراره چه چیزی رو چک کنه به نوع الگو بستگی داره. برای مثال الگوی مقدار ثابت (constant pattern) وقتی تطبیق پیدا میکنه که متغیر با یک مقدار ثابت برابر باشه:

switch (number){
   // Constant pattern matches if 1 == number.
   case 1:
      print('one');
}

خیلی از الگوها از زیرالگوها (subpatterns) استفاده می‌کنند که بهشون الگوهای inner و outer میگیم. الگوها به صورت بازگشتی در زیرالگوهاشون تطبیق داده می‌شن. برای مثال فیلدی که الگوی نوع مجموعه (collection type) داره میتونه خودش الگوی متغیر یا الگوی ثابت داشته باشه:

const a = 'a';
const b = 'b';
switch (obj){
    case [a, b]:
        print('$a, $b');
}

در مثال بالا اول الگوی لیست [a, b] تطبیق داده میشه یعنی چک میشه که آیا obj یک لیست با دو فیلد هست یا نه. بعد چک میشه که آیا فیلدها با زیرالگوهای ثابت 'a' و 'b' مطابق هستن یا نه.

تخریب

وقتی یک object و الگو تطبیق پیدا کنند، الگو می‌تونه به داده های object دسترسی پیدا کنه و اونها رو استخراج کنه. به عبارت دیگه، الگو object رو تخریب می‌کنه:

var numList = [1, 2, 3];
// List pattern [a, b, c] destructures the three elements from numList...
var [a, b , c] = numList;
// ...and assigns them to new variables.
print(a + b + c);

موضوع جالب دیگه اینکه می‌تونید هر نوع الگویی رو داخل الگوی تخریب قرار بدید. در مثال زیر الگوی case یک لیست دو عنصری رو تطبیق میده که عنصر اولش a یا b هست:

switch (list){
   case ['a' || 'b', var c]:
       print(c);
}

کجا می‌تونیم از الگوها استفاده کنیم؟

در زبان دارت از الگوها در موارد زیر می‌تونید استفاده کنید:

  • اعلان و مقداردهی متغیرهای محلی
  • حلقه های for و for-in
  • دستورات if-case و switch-case
  • جریان های کنترلی مثل collection literals

اعلان متغیرها: از الگوی اعلان متغیر (variable declaration) می‌تونید در هرجایی که دارت اجازه‌ی تعریف متغیر محلی میده استفاده کنید. الگو، کار تطبیق مقدار با سمت راست اعلان رو انجام میده. وقتی مطابقت انجام شد، مقدار تخریب میشه و در متغیر محلی جدید قرار می‌گیره:

// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);

الگوی اعلان متغیر باید با var و یا final و سپس الگو شروع بشه.

مقداردهی متغیر: الگوی مقداردهی متغیر (variable assignment)، سمت چپ یک مقداردهی قرار میگیره. اول object تطبیق یافته رو تخریب میکنه. بعد مقدار رو به متغیرهای موجود تخصیص میده (به جای اینکه به یک متغیر جدید مقیدش کنه). مثال زیر رو ببینید که از الگوی مقداردهی برای جابجایی مقدار دو متغیر استفاده می‌کنه و این کار رو بدون استفاده از متغیر موقت سوم انجام میده:

var (a, b) = ('left', 'right');
(b, a) = (a, b);   // Swap
print('$a $b');   // Prints &quotright left&quot

عبارات switch: هر case clause یک الگو داره. در یک case می‌تونید هر نوع الگویی رو استفاده کنید. الگوهای case جریان کنترل برنامه رو به این صورت تغییر می‌دن:

  • آبجکتی که روی اون switch زدیم رو تطبیق می‌ده و تخریب می‌کنه
  • اگر آبجکت تطبیق پیدا نکنه به اجرا ادامه می‌ده

مقادیری که یک الگو تخریب می‌کنه متغیر محلی همون case هستند که تخریب در اون انجام شده. در واقع scope اونها بدنه همون case هست.

switch (obj){
   //Matches if 1 == obj.
   case 1:
     print('one');

   /// Matches if the value of obj is between the constant values of 'first' and 'last'.
   case >= first && <=last :
      print('in range');

   /// Matches if obj is a record with two fields, then assigns the fields to 'a' and 'b'.
   case (var a, var b):
      print('a = $a, b=$b');
   default:
}

الگوهای یا-منطقی (logical-or) وقتی می‌خوایم یک بدنه رو در switch با چند case به اشتراک بگذاریم کاربردی هستند:

var isPrimary = switch (color){
   Color.red || Color.yellow || Color.blue => true,
    _ => false
};

شاید بگید عبارت های switch می‌تونند بدون استفاده از الگوی یا-منطقی هم چند case داشته باشند که یک بدنه رو با هم به اشتراک گذاشتند اما این الگو وقتی می‌خواید چند case یک محافظ (guard) رو به اشتراک بگذارند هم به کار می‌ره و این ویژگی منحصربفردش کرده (به کاربرد when در این مثال دقت کنید):

switch (shape) {
   case Square(size: var s) || Circle(size: var s) when s > 0:
       print('Non-empty symmetric shape');
}

حلقه های for و for-in: الگوها رو می‌تونید در حلقه های for و for-in هم استفاده کنید تا روی عناصر یک مجموعه حرکت کنید و مقدار هر عنصرش رو تخریب کنید. مثال زیر رو ببینید که از تخریب آبجکت در یک حلقه for-in استفاده می‌کنه تا آبجکت های MapEntry که فراخوانی Map>.entries> برمی‌گردونه رو تخریب کنه:

Map<String, int> hist = {
   'a': 23,
   'b': 100,
};
for (var MapEntry(key: key, value: count) in hist.entries){
    print('$key occurred $count times');
}

الگوی آبجکت چک می‌کنه که hist.entries از نوع MapEntry باشه. بعد سراغ زیرالگوی فیلدهای دارای نام key و value می‌ره (اینجاست که الگوها به صورت بازگشتی چک می‌شن). در هر تکرار getter های key و value رو روی MapEntry صدا می‌زنه و نتیجه رو در متغیرهای محلی key و count قرار می‌ده.

تخصیص مقدار برگشتی یک getter به متغیری همنام با خودش یک الگوی رایج هست. برای همین الگوهای آبجکت می‌تونند نام getter رو از زیرالگوی متغیر استنتاج کنند. به این ترتیب از چیز اضافه ای مثل key: key خلاص می‌شیم و میتونیم بنویسیم key:

for (var MapEntry(: key, value: count) in hist.entries){     
     print('$key occurred $count times');
2کار}

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