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