۱۱ - ۱۱تایپ‌های ضرب

ضرب بودنِ یه تایپ یعنی چی؟ کاردینالیتی ِ یه تایپِ ضرب، میشه حاصلضرب ِ کاردینالیتی ِ اعضای اون تایپ. تایپ جمع یا رو بیان می‌کرد، اما در مقابل، تایپ ضرب بیان‌کننده‌ی و هست.

برای اونهایی که قبلاً در زبان‌های مشابه 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‎‏ ِ جدید رو داشته باشن، فایل‌تون رو مجدد بارگذاری کنین و با استفاده از دستگیره‌های فیلد مقادیرِ داخل‌شون رو استعلام کنین.