۱۱ - ۵دادهسازها و مقادیر
کمی قبلتر گفتیم که گزارش هسکل ثابتهای تایپ و نوعسازها رو از هم متمایز میدونه. یه تفاوتِ مشابه بین دادهسازها و مقادیرِ ثابت هم میشه دید.
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!"
چیه؟