۶ - ۶تایپکلاس 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
به مفهومِ اینکه یه چیزی زیرمجموعهی یه مجموعهی بزرگتری از اشیا باشه فکر کنین.