۱۱ - ۸چه چیزی این نوع‌داده‌ها رو جبری می‌کنه؟

نوع‌داده‌های جبری در هسکل به این خاطر جبری اند چون الگوهای ساختارِ آرگومان‌ها رو میشه با دو عملگر ِ پایه توصیف کرد: جمع و ضرب. بهترین راه برای توضیحِ اینکه چرا بهشون جمع و ضرب میگیم، از طریقِ نشون دادنِ جمع و ضرب با کاردینالیتی ‌ِه، همون کاردینالیتی که در مجموعه‌های متناهی هست.* البته این توصیف دقیق نیست، چون در هسکل ساختارهای داده‌ی بینهایت هم وجود دارن، اما راه خوبی برای شروع به درک و تحسین طرزِ کارِ نوع‌داده‌هاست. در زبان‌های برنامه‌نویسی، همه‌ی توابعِ قابلِ محاسبه حائز اهمیت‌اند، نه فقط توابعی که می‌تونن یه مجموعه ایجاد کنن.

*

نظریه‌ی نوع‌ها، به عنوان جایگزینِ نظریه‌ی مجموعه‌ها برای پایه‌ی ریاضیات توسعه داده شد. ما اینجا اثبات‌های صوری رو نمیاریم، اما استدلال‌های غیرصوری که از دیدگاه یه برنامه‌نویس در مورد تایپ‌ها انجام میدیم، تا حدی به ریشه‌شون در مجموعه‌ها برمی‌گرده. مجموعه‌های متناهی شامل یه تعدادی اشیاء ِ یکتا هستن؛ و به این تعداد کاردینالیتی میگیم.

به تعداد مقادیرِ ممکنی که یه نوع‌داده تعریف می‌کنه، کاردینالیتی ِ اون نوع‌داده گفته میشه. این عدد می‌تونه به کوچکی صفر باشه، یا به بزرگی بینهایت (مثلاً نوع‌داده‌های عددی، یا لیست‌ها). دونستنِ اینکه یه نوع‌داده چه تعداد مقادیری می‌تونه داشته باشه به استدلالِ برنامه‌ها کمک می‌کنه. در بخشهای پیشِ رو، نحوه‌ی محاسبه‌ی کاردینالیتی ِ یه نوع‌داده، صرفاً بر مبنای تعریف ِش رو نشون میدیم. از اون نقطه، میشه تعداد پیاده‌سازی‌های ممکن از یه تابع با یک تایپ سیگنچر ِ مشخص رو پیدا کرد.

قبل از اینکه به جزئیات محاسبه‌ی کاردینالیتی بپردازیم، نگاهی اجمالی به ‏‎Bool‎‏ و ‏‎Int‎‏ میندازیم، چون فهمیدن کاردینالیتی ِ اونها آسون ِه.

تا اینجا حسابی ‏‎Bool‎‏ رو بررسی کردیم، و می‌دونیم فقط دو سَکَنه داره که هر دو شون داده‌سازهای پوچگانه اند؛ پس ‏‎Bool‎‏ فقط دو مقدارِ ممکن داره. بنابراین کاردینالیتی ِ ‏‎Bool‎‏ میشه ۲. حتی بدونِ فهمیدنِ قوانینِ کاردینالیتی برای تایپ‌های جمع هم چنین چیزی واضح ِه.

یه مجموعه دیگه‌ی از نوع‌داده‌ها که درکِ کاردینالیتی‌شون نسبتاً آسون ِه، تایپِ ‏‎Int‎‏ ِه. بخشی از دلیلِ این سادگی اینه که تایپِ ‏‎Int‎‏، و همه‌ی تایپ‌های مرتبط باهاش، یعنی ‏‎Int8‎‏، ‏‎Int16‎‏، ‏‎Int32‎‏، و ‏‎Int64‎‏، صراحتاً حد پایین و حد بالا رو، بر مبنای میزان حافظه‌ای که به هرکدوم اختصاص داده میشه مشخص می‌کنن. با اینکه اصلاً در هسکل رایج نیست، ما اینجا از ‏‎Int8‎‏ (که کوچکترین مجموعه‌ی اعضا رو داره) استفاده می‌کنیم تا حساب‌هامون ساده‌تر بشن. مقادیرِ معتبر برای ‏‎Int8‎‏، تمامِ اعدادِ کامل از ۱۲۸- تا ۱۲۷ هستن.

در ‏‎Prelude‎‏ ِ استاندار، برخلافِ ‏‎Int‎‏، ‏‎Int8‎‏ تعریف نشده. بعد از اینکه وارد ِش کنیم، می‌تونیم با ‏‎maxBound‎‏ و ‏‎minBound‎‏ از تایپکلاسِ ‏‎Bounded‎‏ مقادیرِ حد بالا و حد پایین ِش رو ببینیم:

Prelude> import Data.Int
Prelude Data.Int> minBound :: Int8
-128
Prelude Data.Int> maxBound :: Int8
127

با توجه به اینکه این بازه شاملِ صفر هم میشه، با یه جمع ساده میشه کاردینالیتی ِ ‏‎Int8‎‏ رو پیدا کرد: ۲۵۶ = ۱ + ۱۲۷ + ۱۲۸. پس کاردینالیتی ِ ‏‎Int8‎‏ میشه ۲۵۶. هرجا از یه مقدار با تایپ ‏‎Int8‎‏ استفاده کنین، ۲۵۶ مقدارِ زمان اجرا هم وجود داره.

تمرین‌ها: کاردینالیتی

با اینکه هنوز قواعد محاسبه‌ی کاردینالیتی ِ نوع‌داده‌ها رو نگفتیم، احتمالاً راهِ پیدا کردن‌ش برای نوع‌داده‌های ساده با سازنده‌های پوچگانه رو یاد گرفتین. نمی‌خواد زیادی فکر کنین – همون جواب‌هایی که به نظرتون درست میاد رو بنویسین.

۱.

data PugType = PugData

۲.

به خاطر بیارین که ‏‎Bool‎‏ هم با ‏‎|‎‏ تعریف میشه:

data Airline =
       PapuAir
     | CatapultsR'Us
     | TakeYourChancesUnited

۳.

با توجه به چیزهایی که از ‏‎Int8‎‏ دیدیم، کاردینالیتی ِ ‏‎Int16‎‏ چند میشه؟

۴.

با ‏‎maxBound‎‏ و ‏‎minBound‎‏ تایپ‌های ‏‎Int‎‏ و ‏‎Integer‎‏ رو بررسی کنین. چه چیزی از کاردینالیتی ِ اونها می‌تونین بگین؟

۵.

امتیاز اضافه (به دوستاتون پُز بدین!): چه ارتباطی بین ۸ در ‏‎Int8‎‏ و کاردینالیتی ِ ۲۵۶ برای اون تایپ وجود داره؟

نوع‌داده‌های ساده با داده‌سازهای پوچگانه

کاردینالیتی رو با نگاه به نوع‌داده‌هایی که داده‌سازهای پوچگانه دارن شروع می‌کنیم:

data Example = MakeExample deriving Show

اینجا ‏‎Example‎‏ نوع‌ساز، و ‏‎MakeExample‎‏ تنها داده‌ساز ِه؛ و چون هیچ آرگومانی نمی‌گیره، سازنده ِ پوچگانه هست. می‌دونیم که داده‌سازهای پوچگانه مقادیرِ ثابت اند و فقط خودشون رو به عنوان مقدار ارائه میدن. این یه مقدار مجرد ِه که تنها محتویات‌ش اسم‌شه، و نه داده ِ دیگه‌ای. در شمارشِ کاردینالیتی، سازنده‌های پوچگانه فقط یکی به کاردینالیتی ِ تایپ‌شون اضافه می‌کنن.

تنها چیزی که درباره‌ی ‏‎MakeExample‎‏ میشه گفت اینه که سازنده ِ مقدارِ ‏‎MakeExample‎‏ ِه، و در تایپِ ‏‎Example‎‏ سکونت داره.

پس تنها سَکَنه‌ی ‏‎Example‎‏، ‏‎MakeExample‎‏ ِه. با توجه به اینکه ‏‎MakeExample‎‏ یه مقدارِ پوچگانه ِ مجرد ِه، در نتیجه کاردینالیتی ِ ‏‎Example‎‏ هم میشه ۱. با دونستنِ این عدد، هر وقت در تایپ سیگنچر ِ یه تابع ‏‎Example‎‏ رو ببینیم، میدونیم که فقط یک مقدار رو باید در نظر بگیریم.

تمرین‌ها: برای مثال

۱.

میشه تایپِ یه مقدار رو با دستورِ ‏‎:type‎‏ یا به طور خلاصه ‏‎:t‎‏ در GHCi استعلام کرد.

مثال:

Prelude> :t False
False :: Bool

تایپِ داده‌ساز ِ ‏‎MakeExample‎‏ چیه؟ چه اتفاقی میوفته اگه تایپِ ‏‎Example‎‏ رو استعلام کنین؟

۲.

اگه اطلاعاتِ ‏‎Example‎‏ رو با ‏‎:info‎‏ در GHCi استعلام کنین چطور؟ می‌تونین از اون طریق بگین چه نمونه تایپکلاس‌هایی برای تایپِ ‏‎Example‎‏ تعریف شدن؟

۳.

سعی کنین یه نوع‌داده ِ دیگه مثلِ ‏‎Example‎‏ درست کنین، اما این بار یه آرگومان، مثل ‏‎Int‎‏، هم به ‏‎MakeExample‎‏ اضافه کنین. چه تغییراتی در نتیجه‌ی استعلام ِ تایپ‌ش در GHCi پیش میاد؟

سازنده‌های ‌یگانی

در تمرین‌ها ازتون خواستیم که یک آرگومانِ تایپی به داده‌ساز ِ ‏‎MakeExample‎‏ اضافه کنین. با این کار از یه سازنده ِ پوچگانه، به یه سازنده ِ یگانی تبدیل‌ش کردین. داده‌سازهای یگانی یک آرگومان می‌گیرن. در تعریفِ داده، چنین پارامتری مقدار نیست، بلکه تایپ ِه. حالا بجای اینکه داده‌ساز یه مقدارِ ثابت یا مشخص باشه، مقدارش از آرگومانی که می‌گیره، در زمان اجرا ساخته میشه.

نوع‌داده‌هایی که فقط شامل یک سازنده ِ یگانی هستن، همواره همون کاردینالیتی ای رو دارن که تایپِ مشمول‌شون داره. در مثال زیر، ‏‎Goats‎‏ همون تعداد عضوی رو داره که ‏‎Int‎‏ داره:

data Goats = Goats Int deriving (Eq, Show)

هر چیزی که بتونه یه ‏‎Int‎‏ باشه، یه آرگومان قابل استفاده برای سازنده ِ ‏‎Goats‎‏ هم هست. هر چیزی هم که یه ‏‎Int‎‏ ِ معتبر نباشه، برای ‏‎Goats‎‏ هم معتبر نیست.

اگه از جنبه‌ی کاردینالیتی نگاه کنیم، سازنده‌های یگانی، تابع همانی هستن.