۶ - ۶تایپکلاس Num
تا حالا دیگه خیلی Num دیدیم، پس سعی میکنیم این بخش طولانی نشه. اکثرِ تایپهای عددی این تایپکلاس رو دارن. دوباره مثلِ Eq اطلاعاتش رو استعلام میکنیم و توابعِ از پیش تعریف شدهش رو بررسی میکنیم:
class Num a where
(+) :: a -> a -> a
(*) :: a -> a -> a
(-) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> aو لیستِ نمونههاش (کامل نیست):
instance Num Integer
instance Num Int
instance Num Float
instance Num Doubleبیشترِ این اطلاعات رو قبلاً دیدیم: توابعِ حساب رو با تایپ سیگنچر ِشون در بالا نوشتیم (تابعِ fromInteger مثل fromIntegral میمونه با این تفاوت که بجای همهی Integral ها فقط به Integer محدود شده)، پایینتر هم یه لیست از تایپهایی که این تایپکلاس رو دارن – تایپهای عددیای که قبلاً دیدیم. چیزی جدید نیست.
تایپکلاسِ Integral
این تایپکلاس تعریفِ زیر رو داره:
class (Real a, Enum a) => Integral a where
quot :: a -> a -> a
rem :: a -> a -> a
div :: a -> a -> a
mod :: a -> a
quotRem :: a -> a -> (a, a)
divMod :: a -> a -> (a, a)
toInteger :: a -> Integerمحدودیت تایپکلاسی ِ (Real a, Enum a) => میگه که هر تایپی که تایپکلاسِ Integral داره، باید هر دو تایپکلاسهای Real و Enum رو هم داشته باشه. اون گرامر ِ توپل، دقیقاً عطفِ منطقی ِ محدودیتهای تایپکلاسی رو روی متغیرهای تایپ اعمال میکنه. یه تایپِ Integral، هم باید یه عددِ حقیقی باشه و هم باید قابل شمارش باشه، بنابراین میتونه از متودهای هر دوی اون تایپکلاسها بهره ببره. تایپکلاسِ Real هم تایپکلاسِ Num رو لازم داره. پس تایپکلاس Integral متودهای Real و Num رو در اختیار داره (به اضافهی متودهای Enum). از اونجا که Real نمیتونه متودهای Num رو پایمال کنه، این وراثت ِ تایپکلاسها فقط افزایشی ِه و در نتیجه ابهاماتِ ناشی از چند وراثتی (معروف به "الماس کشندهی مرگ") که در بعضی زبانهای دیگه وجود داره از بین میره.
تمرینها: آزمایش توپل
تایپِ توابعِ quotRem و divMod رو نگاه کنین. فکر میکنین چه کاری انجام میدن؟ تو REPL باهاشون بازی کنین و ببینین نظریهتون درسته یا نه. یه مثال برای شروع کار نوشتیم:
Prelude> let ones x = snd (divMod x 10)تایپکلاسِ Fractional
Num اَبرکلاس ِ Fractional ِه. تایپکلاسِ Fractional اینطوری تعریف میشه:
class (Num a) => Fractional a where
(/) :: a -> a -> a
recip :: a -> a
fromRational :: Rational -> aاین تعریفِ تایپکلاس، یه کلاس به اسم Fractional میسازه که برای ساختنِ نمونهش، آرگومانِ تایپیش (a) باید یه نمونه از Num داشته باشه. این یه مثالِ دیگه از وراثت ِ تایپکلاسهاست. Fractional نسبت به Num، به اعدادِ کمتری اعمال میشه و نمونههای Fractional میتونن از توابعِ تعریف شده در Num استفاده کنن، ولی از اونجا که هیچ چیزی در تعریفِ Num ملزوم به داشتنِ Fractional نیست، همهی Num ها به توابعِ Fractional دسترسی ندارن. انتهای فصل یه نمودار هست که به درک بصری این روابط کمک میکنه.
با توابع عادی هم میشه دید:
اول این تابع رو در نظر بگیریم (عمداً تایپش رو ننوشتیم):
divideThenAdd x y = (x / y) + 1این تابع رو با تایپی که فقط Num داشتن رو الزام میکنه بارگذاری میکنیم:
divideThenAdd :: Num a => a -> a -> a
divideThenAdd x y = (x / y) + 1و خطای تایپ میگیریم:
Could not deduce (Fractional a)
arising from a use of ‘/’
from the context (Num a)
bound by the type signature for
divideThenAdd :: Num a => a -> a -> aحالا اگه فقط محدودیت ِ Num برامون مهم بود، میشد تابعِ (/) رو که Fractional لازم داره از تابعمون حذف کنیم:
-- این کار میکنه
-- هم (+) و هم (-) رو تأمین میکنه Num
subtractThenAdd :: Num a => a -> a -> a
subtractThenAdd x y = (x - y) + 1یا میتونیم بجای خودِ تابع، فقط تایپش رو عوض کنیم:
-- این هم کار میکنه
divideThenAdd :: Fractional a => a -> a -> a
divideThenAdd x y = (x / y) + 1کلاه تفکرتون رو بپوشین
چرا لازم نبود هر دو تایپکلاس رو برای تابعمون اجبار کنیم؟ چرا اینطوری ننوشتیم:
f :: (Num a, Fractional a) => a -> a -> aبه مفهومِ اینکه یه چیزی زیرمجموعهی یه مجموعهی بزرگتری از اشیا باشه فکر کنین.