۱۱ - ۱۱تایپهای ضرب
ضرب بودنِ یه تایپ یعنی چی؟ کاردینالیتی ِ یه تایپِ ضرب، میشه حاصلضرب ِ کاردینالیتی ِ اعضای اون تایپ. تایپ جمع یا رو بیان میکرد، اما در مقابل، تایپ ضرب بیانکنندهی و هست.
برای اونهایی که قبلاً در زبانهای مشابه C برنامهنویسی کردن، یه ضرب، مثل struct میمونه. برای اونهایی که چنین تجربهای ندارن، ضرب راهی برای نگهداریِ چند مقدار در یک دادهساز ِه. هر دادهسازی که دو یا بیشتر آرگومان تایپی داشته باشه، یه ضرب ِه.
قبلاً گفتیم که توپلها، ضربهای بینام اند. تعریفِ تایپِ توپل رو ببینین:
( , ) :: a -> b -> (a, b)این هم مثل تایپ ضرب، امکانِ نگهداریِ دو داده از تایپهای (نه لزوماً) متفاوت در یک مقدار رو فراهم میکنه.
یه تایپ جمع ِ دیگه رو ببینیم:
data QuantumBool = QuantumTrue
| QuantumFalse
| QuantumBoth
deriving (Eq, Show)این تایپِ جمع چه کاردینالیتی ای داره؟
به دلایلی که به زودی روشن میشن، استفاده از کاردینالیتی ِ ۲ برای نشون دادن تفاوتِ بین کاردینالیتی ِ جمع و ضرب خیلی مناسب نیست، به همین خاطر از QuantumBool که کاردینالیتی ِ ۳ داره استفاده میکنیم. یه تایپ ضرب که دو مقدارِ QuantumBool داره تعریف میکنیم:
data TwoQs =
MkTwoQs QuantumBool QuantumBool
deriving (Eq, Show)نوعداده ِ TwoQs یک دادهساز ِ MkTwoQs داره که دو آرگومان میگیره، و در نتیجه TwoQs میشه ضرب ِ دو تایپی که داخلش هستن. تایپِ هر آرگومان هم QuantumBool ِه که کاردینالیتی ِ ۳ داره.
برای تجسمِ بهتر میتونین چنین چیزی بنویسین. یه مقدارِ MkTwoQs میتونه اینها باشه:
MkTwoQs QuantumTrue QuantumTrue
MkTwoQs QuantumTrue QuantumFalse
MkTwoQs QuantumTrue QuantumBoth
MkTwoQs QuantumFalse QuantumFalse
-- و الی آخردقت کنین برخلاف تایپِ جمع که با گرامر ِ | مشخص میشد، تایپ ضرب هیچ گرامر ِ خاصی نداره. MkTwoQs دادهسازی ِه که دو آرگومان میگیره، و اینجا بر حسب اتفاق هر دوشون یک تایپاند. پس یک تایپِ ضرب ِه؛ حاصلضرب ِ دو QuantumBool. تعداد مقادیری که ممکنه با این تایپ درست بشه، معادلِ حاصلضرب کاردینالیتی ِ آرگومانهای تایپیش میشه. پس کاردینالیتی ِ TwoQs چی میشه؟
تایپِ TwoQs رو با تایپ مستعار و دادهساز ِ توپل هم میشد بنویسیم. تایپهای مستعار نوعساز درست میکنن، نه دادهساز:
type TwoQs = (QuantumBool, QuantumBool)این هم همون کاردینالیتی رو نتیجه میده.
فهمیدنِ کاردینالیتی به این دلیل مهمه که کاردینالیتی ِ یه نوعداده تقریباً میزانِ سختی در استدلالِ اون تایپ رو نشون میده.
گرامر رکورد
در هسکل، رکوردها همون تایپهای ضرب اند که با یه گرامر ِ اضافی، دسترسی به فیلدهای درونِ رکورد رو آسون میکنن. با یه تایپِ ضرب ِ ساده شروع میکنیم:
data Person =
MkPerson String Int
deriving (Eq, Show)با این ساختارِ تایپِ ضرب آشنا هستیم: دادهساز ِ MkPerson دو آرگومان تایپی میگیره، یکی String (به عنوان اسم) و یک Int (سن). کاردینالیتی ِ ترسناکی داره...
در مثالها دیدیم که چطور میشه با توابع محتویات این تایپ رو باز کرد و مقدار مورد نظر رو از تو اون جعبهی مقادیر درآورد:
-- دادههای نمونه
jm = Person "julie" 108
ca = Person "chris" 16
namae :: Person -> String
namae (MkPerson s _) = sاگه از تابعِ namae در REPL استفاده کنین، مقدارِ String ِ داده رو برمیگردونه.
حالا ببینیم تعریفِ یه تایپ ضرب ِ مشابه با گرامر رکورد چطور میشه:
data Person =
Person { name :: String
, age :: Int }
deriving (Eq, Show)شبیهِ تایپِ Person که بالاتر تعریف کردیم ِه، ولی حالا که با رکورد تعریف کردیم، دسترسی به محتویاتش از طریقِ اسمِ فیلدها میسر شده. اون اسمها تابعهایی هستن که یه مقدار با تایپِ ضرب رو میگیرن و یکی از اعضای اون ضرب رو برمیگردونن:
Prelude> :t name
name :: Person -> String
Prelude> :t age
age :: Person -> Intمیشه مستقیم در REPL استفاده کرد:
Prelude> Person "Papu" 5
Person {name = "Papu", age = 5}
Prelude> let papu = Person "Papu" 5
Prelude> age papu
5
Prelude> name papu
"Papu"با داده ای که در یه فایل نوشته شده هم میشه چنین کاری کرد. دادههای jm و ca که بالاتر تعریف کردیم رو تغییر بدین تا تایپِ Person ِ جدید رو داشته باشن، فایلتون رو مجدد بارگذاری کنین و با استفاده از دستگیرههای فیلد مقادیرِ داخلشون رو استعلام کنین.