۱۱ - ۵دادهسازها و مقادیر
کمی قبلتر گفتیم که گزارش هسکل ثابتهای تایپ و نوعسازها رو از هم متمایز میدونه. یه تفاوتِ مشابه بین دادهسازها و مقادیرِ ثابت هم میشه دید.
data PugType = PugData
-- [1] [2]
data HuskyType a = HuskyData
-- [3] [4]
data DogueDeBordeaux doge = DogueDeBordeaux doge
-- [5] [6]۱.
PugType یه نوعساز ِه که هیچ آرگومانی نمیگیره، پس میشه گفت یه ثابت تایپ ِه (همونطور که در گزارش هسکل اومده). این تایپ فقط یک سازنده رو میشماره.
۲.
PugData تنها دادهساز برای تایپِ PugType ِه. اتفاقاً هم یه مقدارِ ثابت ِه (هیچ آرگومانی نمیگیره). هر وقت تابعی ببینیم که یه مقدار با تایپِ PugType لازم داره، میدونیم که اون مقدار حتماً PugData هست.
۳.
HuskyType یه نوعساز ِه، و یک متغیر تایپ با پلیمورفیسم ِ پارامتریک به عنوان آرگومان میگیره. یک دادهساز هم شمارش میکنه.
۴.
HuskyData دادهساز برای HuskyType ِه. دقت کنین که متغیر تایپ ِ a به عنوانِ آرگومان برای HuskyData نیومده، و اصلاً هیچجا بعد از = نوشته نشده. یعنی آرگومان تایپیِ a خیالی ِه؛ به عبارت دیگه "شاهد نداره." بعداً دربارهی این بیشتر صحبت میکنیم. HuskyData هم مثلِ PugData یه مقدارِ ثابت ِه.
۵.
DogueDeBordeaux هم مشابهِ HuskyType یه نوعساز با یک متغیرِ تایپی ِه، فقط اسم آرگومانش بجای a شده doge. چرا؟ چون اسم متغیرها اهمیتی ندارن. این تایپ هم فقط یک سازنده رو میشماره.
۶.
تنها دادهساز برای DogueDeBordeaux. این دادهساز با خودِ نوعساز هماسم ِه، اما این دو تا یک چیز نیستن. متغیرِ تایپی ِ doge در نوعساز، در دادهساز هم ظاهر شده؛ و چون این دو تا یه متغیر تایپ اند، پس باید با هم یکی باشن: doge برابرِ doge. مثلاً اگه تایپ DogueDeBordeux [Person] هست، باید یه لیست از Person داخلِ مقدارِ DogueDeBordeux باشه. از اونجا که برای رسیدن به یه مقدارِ معیّن، DogueDeBordeux باید اول اعمال بشه، هر مقداری در زمان اجرا میشه باهاش ساخت.
تایپِ مقدار رو میشه استعلام کرد (تایپِ دادهساز، نه نوعساز – اینطور وقتها که اسمِ نوعساز و دادهساز یکیه، ممکنه گیجکننده باشه، اما این کار خیلی تو هسکل رایجه. کامپایلر مثل ما فانیها اسم تایپها رو با اسم مقادیر قاطی نمیکنه):
Prelude> :t DogueDeBordeaux
DogueDeBordeaux :: doge
-> DogueDeBordeaux dogeاین تایپ سیگنچر میگه وقتی doge به یه تایپِ معیّن مقیّد میشه، بعدش یه مقدار با تایپِ DogueDeBordeaux doge میشه. یعنی این الان یه مقدار نیست، بلکه تعریفی از نحوهی ساختِ مقداری از اون تایپه.
حالا از هرکدوم از این تایپها یه مقدار میسازیم:
myPug = PugData :: PugType
myHusky :: HuskyType a
myHusky = HuskyData
myOtherHusky :: Num a => HuskyType a
myOtherHusky = HuskyData
myOtherOtherHusky :: HuskyType [[[[Int]]]]
myOtherOtherHusky = HuskyData
-- برخلاف ظاهر، شاهدی نداره ^در مثال زیر، عدد ۱۰ با Int که متغیر تایپی بهش مقیّد شده همخونی داره، به همین خاطر هم تایپچک میشه:
myDoge :: DogueDeBordeaux Int
myDoge = DogueDeBordeaux 10اما در این مثال، ۱۰ با انقیاد ِ متغیرِ تایپی به String جور نیست و کار نمیکنه:
badDoge :: DogueDeBordeaux String
badDoge = DogueDeBordeaux 10تو این چندتا مثال دیدیم که با سازندهها میشه مقادیرِ تایپهای مختلف رو ساخت و در تایپ سیگنچرها به تایپها اشاره کرد. یه توازی بین دادهسازها و نوعسازها وجود داره که با یه نوعداده ِ دیگه نشون میدیم:
data Doggies a =
Husky a
| Mastiff a
deriving (Eq, Show)
-- نوعسازی که منتظر یه آرگومانه
Doggiesدقت کنین که کایند سیگنچر ِ نوعساز، و تایپ سیگنچر ِ هر کدوم از دادهسازهاش، همگی مشابهِ تابع میمونن.
این نوعساز باید اعمال بشه تا یه تایپِ معیّن بده:
Prelude> :k Doggies
Doggies :: * -> *و این دادهساز باید اعمال بشه تا یه مقدارِ معیّن بده:
Prelude> :k Husky
Husky :: a -> Doggies aپس سازندهها اگه هیچ آرگومانی نگیرن، مثل ثابتها رفتار میکنن (ثابت ِ تایپی یا مقداری). اگر هم آرگومان بگیرن، مشابه توابعی (مقداری یا تایپی) رفتار میکنن که به غیر از اعمال شدن، کار دیگهای انجام نمیدن.
تمرینها: انواع سگ
با فرضِ نوعدادههای تعریف شده در بخش بالا،
۱.
آیا Doggies یه نوعساز ِه یا یه دادهساز؟
۲.
گونه یا کایندِ Doggies چیه؟
۳.
کایندِ Doggies String چیه؟
۴.
تایپِ Husky 10 چیه؟
۵.
تایپِ Husky (10 :: Integer) چیه؟
۶.
تایپِ Mastiff "Scooby Doo" چیه؟
۷.
آیا DogueDeBordeaux یه نوعساز ِه یا یه دادهساز؟
۸.
تایپِ DogueDeBordeaux چیه؟
۹.
تایپِ DogueDeBordeaux "doggie!" چیه؟