۱۱ - ۱۲حالت معمولی

تا اینجا مفهومِ جبر در نوع‌داده‌های جبریِ هسکل رو بررسی کردیم و فوایدِ درکِ کاردینالیتی ِ نوع‌داده‌ها رو شناختیم. اما جبر به همینجا ختم نمیشه. همه‌ی قواعد جبری که برای ضرب و جمع وجود دارن، برای تایپ سیستم هم صادق‌اند. خاصیتِ توزیع‌پذیری هم یکی از این قواعده. با یه مثال نحوه‌ی کارش رو در حساب نشون میدیم:

2 * (3 + 4)
  2 * (7)
    14

میشه از توزیع ِ ضرب روی جمع هم به همین جواب برسیم:

2 * 3 + 2 * 4
  (6) + (8)
    14

به این میگیم "جمعِ ضرب‌ها." در حساب، حالت معمولی برای چنین بیانیه‌ای، بعد از رسیدن به جواب‌ه (م. یعنی ۱۴ در مثال بالا). اما اگه اون اعداد در مثالِ بالا نشون‌دهنده‌ی کاردینالیتی ِ چندتا مجموعه باشن، اون موقع همون بیانیه‌ی جمعِ ضرب‌ها حالت معمولی محسوب میشه، چون محاسبه‌ای باقی نمونده.

خاصیتِ توزیع‌پذیری رو میشه تعمیم داد:

a * (b + c) -> (a * b) + (a * c)

و این برای تایپ‌های هسکل هم صادق ِه! تایپ‌های ضرب روی تایپ‌های جمع توزیع میشن. برای اینکه بیشتر با این بازی کنیم، اول چندتا نوع‌داده تعریف می‌کنیم:

data Fiction = Fiction deriving Show
data Nonfiction = Nonfiction deriving Show

data BookType = FictionBook Fiction
              | NonfictionBook Nonfiction
              deriving Show
-- BookType : م. نوعِ کتاب

دو تایپی که هرکدوم فقط یک عضوِ پوچگانه دارن تعریف کردیم: ‏‎Fiction‎‏ و ‏‎Nonfiction‎‏. دلایل چنین کاری شاید درجا واضح نباشه، اما به خاطر بیارین که گفتیم از یه تایپ، فقط با یکی از مقادیرش نمیشه استفاده کرد. نمیشه یه مقدارِ ‏‎Bool‎‏ بخواین و در تایپِ بیانیه مشخص کنین که باید همیشه ‏‎True‎‏ باشه – مجبورین هر دو مقدارِ ‏‎Bool‎‏ رو در نظر بگیرین. پس با تعریفِ ‏‎Fiction‎‏ و ‏‎Nonfiction‎‏ می‌تونیم انواع کتاب (که زیرش تعریف کردیم) رو تفکیک کنیم.

یه تایپ جمع به اسمِ ‏‎BookType‎‏ هم تعریف کردیم که سازنده‌هاش تایپ‌های ‏‎Fiction‎‏ و ‏‎Nonfiction‎‏ رو به عنوان آرگومان می‌گیرن. به خاطر داشتنِ این مهم ِه، با اینکه اسمِ نوع‌سازها و داده‌سازهای ‏‎Fiction‎‏ و ‏‎Nonfiction‎‏ یکی‌اند، اما این دو تا یک چیز نیستن. چیزی که به عنوان آرگومان به ‏‎FictionBook‎‏ و ‏‎NonfictionBook‎‏ داده میشه، نوع‌سازهااند. اسم‌هاشون رو عوض کنین و خودتون تأثیرش رو ببینین.

حالا یه تایپ مترادف به اسمِ ‏‎AuthorName‎‏ و یه تایپ ضرب به اسمِ ‏‎Author‎‏ تعریف می‌کنیم. تایپ مترادف واقعاً کارِ خاصی نمی‌کنه، فقط اینجا کمک می‌کنه بدونیم از کدوم ‏‎‎‏String در تایپِ ‏‎Author‎‏ داریم استفاده می‌کنیم:

type AuthorName = String

data Author = Author (AuthorName, BookType)

این جمعِ ضرب نیست، پس حالت معمولی هم نیست. میشه طوری محاسبه‌ش کرد که مقادیرِ مخفی شده داخلِ تایپِ جمع ِ ‏‎BookType‎‏ از توش بیرون کشیده بشن. با اعمال خاصیت توزیع‌پذیری تایپِ ‏‎Author‎‏ رو در حالت معمولی بازنویسی می‌کنیم:

type AuthorName = String

-- اگه هر دوشون رو تو یه
-- فایل نوشتین، باید تعاریف
-- Nonfiction و Fiction قبلی از
-- .کنین یا پاک کنین comment رو

data Author =
    Fiction AuthorName
  | Nonfiction AuthorName
  deriving (Eq, Show)

ضرب‌ها روی جمع‌ها توزیع میشن. مثل باز کردن بیانیه‌ی ‏‎a * (b + c)‎‏ (که اعضای ‏‎BookType‎‏ معادلِ ‏‎b‎‏ و ‏‎c‎‏ میشن)، با مجزا کردن مقادیر به جمعِ ضرب‌ها رسیدیم. حالا در حالت معمولی ِه، چون تا وقتی عملیات یا محاسبه‌ای با این تایپ‌ها انجام نشه، این سازنده‌ها ساده‌تر نمیشن.

یه مثالِ دیگه از حالت معمولی رو با تایپ ‏‎Expr‎‏، که در خیلی از مقالات درباره‌ی تایپ سیستم‌ها و زبان‌های برنامه‌نویسی به کار میره نشون میدیم:

data Expr =
    Number Int
  | Add Expr Expr
  | Minus Expr
  | Mult Expr Expr
  | Divide Expr Expr

این جمعِ ضرب‌ها هست (تایپ جمع از تایپ‌های ضرب)، پس در حالت معمولی ِه: ‏‎(Number Int) + Add (Expr Expr) + …‎‏

اگه بخوایم سخت‌گیرانه‌تر به حالت معمولی ِ "جمعِ ضرب‌ها" برسیم، باید ضرب‌ها رو با توپل، و جمع‌ها رو با ‏‎Either‎‏ نشون بدیم. پس نوع‌داده ِ بالا مثل زیر میشه:

type Number = Int
type Add = (Expr, Expr)
type Minus = Expr
type Mult = (Expr, Expr)
type Divide = (Expr, Expr)

type Expr =
  Either Number
    (Either Add
      (Either Minus
        (Either Mult Divide)))

چنین ارائه‌ای در جاهایی مثل برنامه‌نویسیِ جِنِریک یا فرابرنامه‌نویسی که توابع یا فولدها برای نوع‌داده‌ها نوشته میشن کاربرد داره. بعضی از این متودها در هسکل هم کاربرد دارن، اما باید با دقت زیاد به کار برن، و همیشه هم استفاده ازشون آسون نیست.

تایپِ ‏‎Either‎‏ رو در فصل بعد کامل توضیح میدیم.

تمرین‌ها: رشد باغچه‌تون چطوره؟

۱.

با توجه به تایپ زیر

data FlowerType = Gardenia
                | Daisy
                | Rose
                | Lilac
                deriving Show

type Gardener = String

data Garden =
  Garden Gardener FlowerType
  deriving Show

حالت معمولی (جمعِ ضرب) ِ ‏‎Garden‎‏ چی میشه؟