۱۱ - ۸چه چیزی این نوعدادهها رو جبری میکنه؟
نوعدادههای جبری در هسکل به این خاطر جبری اند چون الگوهای ساختارِ آرگومانها رو میشه با دو عملگر ِ پایه توصیف کرد: جمع و ضرب. بهترین راه برای توضیحِ اینکه چرا بهشون جمع و ضرب میگیم، از طریقِ نشون دادنِ جمع و ضرب با کاردینالیتی ِه، همون کاردینالیتی که در مجموعههای متناهی هست.* البته این توصیف دقیق نیست، چون در هسکل ساختارهای دادهی بینهایت هم وجود دارن، اما راه خوبی برای شروع به درک و تحسین طرزِ کارِ نوعدادههاست. در زبانهای برنامهنویسی، همهی توابعِ قابلِ محاسبه حائز اهمیتاند، نه فقط توابعی که میتونن یه مجموعه ایجاد کنن.
نظریهی نوعها، به عنوان جایگزینِ نظریهی مجموعهها برای پایهی ریاضیات توسعه داده شد. ما اینجا اثباتهای صوری رو نمیاریم، اما استدلالهای غیرصوری که از دیدگاه یه برنامهنویس در مورد تایپها انجام میدیم، تا حدی به ریشهشون در مجموعهها برمیگرده. مجموعههای متناهی شامل یه تعدادی اشیاء ِ یکتا هستن؛ و به این تعداد کاردینالیتی میگیم.
به تعداد مقادیرِ ممکنی که یه نوعداده تعریف میکنه، کاردینالیتی ِ اون نوعداده گفته میشه. این عدد میتونه به کوچکی صفر باشه، یا به بزرگی بینهایت (مثلاً نوعدادههای عددی، یا لیستها). دونستنِ اینکه یه نوعداده چه تعداد مقادیری میتونه داشته باشه به استدلالِ برنامهها کمک میکنه. در بخشهای پیشِ رو، نحوهی محاسبهی کاردینالیتی ِ یه نوعداده، صرفاً بر مبنای تعریف ِش رو نشون میدیم. از اون نقطه، میشه تعداد پیادهسازیهای ممکن از یه تابع با یک تایپ سیگنچر ِ مشخص رو پیدا کرد.
قبل از اینکه به جزئیات محاسبهی کاردینالیتی بپردازیم، نگاهی اجمالی به 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 هم معتبر نیست.
اگه از جنبهی کاردینالیتی نگاه کنیم، سازندههای یگانی، تابع همانی هستن.