۴ - ۴تایپ‌های عددی

اعداد رو کمی در فصل‌های گذشته دیدیم، حالا اینجا با جزئیات بیشتری بررسی شون می‌کنیم. مهمه که بدونیم هسکل فقط از یک تایپ برای اعداد استفاده نمی‌کنه. تایپِ عددهایی که بیشتر باهاشون سر و کار داریم، اینها هستن:

اعدادِ صحیح

اعداد تام یا کامل‌اند، چه مثبت و چه منفی.

۱.

تایپ Int: این تایپ، عدد صحیح با دقتِ ثابت ِه. منظور از دقت ثابت اینه که محدوده دارن، با یه مقدار حداکثر و یه مقدار حداقل؛ و نمی‌تونن هر چقدر که می‌خوایم بزرگ یا کوچک باشن – کمی جلوتر بیشتر توضیح میدیم.

۲.

تایپ Integer: این تایپ هم برای اعداد صحیح ِه، ولی اعدادش محدوده‌ای ندارن (هر چقدر که بخوایم بزرگ یا کوچک میشن).

کسری

این اعداد صحیح نیستن. مقادیر Fractional شامل این چهار تایپ‌اند:

۱.

تایپ Float: این تایپ برای اعداد اعشاری ِتک-دقتی به کار میره. اعدادِ ممیز-ثابت، تعداد ثابتی از ارقام برای قبل و بعد از ممیز دارن. در مقابل، ممیز شناور می‌تونه تعداد بیت‌هایی که برای اعداد قبل و بعد از ممیز استفاده میشن رو جابجا کنه. البته این انعطاف‌پذیری منجر به نقضِ بعضی فرضیه‌های مشترک برای اعداد میشه، و بهتره با نهایتِ دقت از اونها استفاده بشه. به طور کلی، در کاربردهای تجاری اصلاً نباید از اعداد با نقطه‌ی شناور استفاده کرد.

۲.

تایپ Double: تایپی برای اعداد اعشاریِ دو-دقتی. مشابه تایپ Float، با تفاوت اینکه دو برابر بیت برای توصیف اعداد دارن.

۳.

تایپ Rational: نوعی عدد کسری که نسبتِ دو عدد صحیح رو نشون میده. مقدار Rational:: 7 / 5 حامِل دو مقدار Integer ِه: یکی صورت ۵، و اون یکی مخرج ۷.Rational دقت نامحدود داره، ولی به اندازه‌ی Scientificمقرون به صرفه نیست.

۴.

تایپ Scientific: نوعی از اعداد که فضای مناسبی رو اشغال می‌کنن، و دقت تقریباً نامحدودی دارند. اعداد Scientific با نماد علمی نشون داده میشن، و متشکل از یک ضریب از تایپ Integer، و یک توان از تایپ Int هستن. به خاطر محدودیت Int، در حقیقت اعداد Scientific هم محدودن، ولی خیلی کم پیش میاد به اون حد برسیم. تایپ Scientific در یه کتابخونه تعریف شده، و میشه با دستور cabal install یا stack install نصب‌ش کرد.

همه‌ی این نوع‌داده‌ها یک نمونه از تایپکلاسی به اسمِ Num دارن. ما در فصل‌های بعدی تایپکلاس‌ها رو توضیح میدیم، ولی تو این بخش Num رو در کنار اطلاعات تایپ‌ها می‌بینین.

با تایپکلاس‌ها میشه قابلیت‌هایی رو به تایپ‌ها اضافه کرد که برای همه‌ی تایپ‌های دارای یه نمونه از اون تایپکلاس، قابل استفاده باشن. Num تایپکلاسی‌ه که اکثر تایپ‌های عددی یک نمونه ازَش دارند، دلیل‌ش وجودِ توابع استانداردی‌ه که برای همه‌ی تایپ‌های عددی قابلِ استفاده‌اند. تایپکلاس Num چیزیه که عملگرهای استاندارد مثل (+)،(-)، و (*) و چندتای دیگه رو در اختیار اعداد قرار میده. میشه این توابع رو به هر تایپی که یه نمونه از تایپکلاس Num داره اعمال کنیم. یک نمونه، چگونگیِ عملکردِ توابع برای یک تایپ خاص رو تعریف می‌کنه. به زودی تایپکلاس‌ها رو با جزئیات خیلی بیشتر توضیح میدیم.

اعداد صحیح

همونطور که بالاتر گفتیم، اعدادِ صحیح، دو تایپِ اصلی دارن: Int و Integer.

اعداد صحیح، اعداد کامل و بدون اعشارند. مثل اینها:

1 2 199 32442353464675685678

اما اینها صحیح نیستند:

1.3 1/2

Integer

اینها همون اعدادِ کاملی‌اند که بهشون عادت داریم. می‌تونن مثبت یا منفی باشن، و هر چقدر هم لازم باشه بزرگ یا کوچک میشن.

تایپِ Bool فقط دو مقدار داره که میشه صراحتاً به عنوان داده‌ساز بنویسیمِ‌شون. در مورد Integer، و بیشتر نوع‌داده‌های عددی که بینهایت مقدار دارن، داده‌ساز هاشون نوشته نمیشن. از نظر تئوری، یک Integer رو میشه با جمعِ سه حالتِ: صفر، داده‌سازهای بازگشتی (خوداِتکا) به سمت منفی بینهایت، و داده‌سازهای بازگشتی به سمت مثبت بینهایت تعریف کرد. ولی چنین روشی اصلاً مقرون به صرفه نیست، به همین خاطر، در این مورد GHC یه کم جادو می‌کنه.

چرا Int داریم؟

تایپِ عددیِ Int در واقع بازمونده‌ای از امکاناتِ اولیه‌ی کامپیوترهاست. اکثر برنامه‌ها باید با Integer کار کنن، نه Int؛ مگر اینکه برنامه‌نویس احاطه‌ی کامل به محدودیتهای Int داشته باشه، و اینکه عملکرد ِاضافه‌ی حاصل از اونها واقعاً مطلوب باشه.

خطرِ استفاده از Int (و تایپ‌های مشابه مثل Int8، Int16 و غیره) اینه که نمی‌تونن اطلاعاتِ خیلی بزرگ رو بَیان کنن. پس از اونجا که اینها اعدادِ صحیح هستن، نمی‌تونن در جهت مثبت یا منفی اندازه‌ی دلخواه داشته باشن.

تو این مثال می‌بینید که وقتی از عددی بزرگتر از حدّ Int8 استفاده می‌کنیم، چه اتفاقی میوفته:

Prelude> import GHC.Int
Prelude> 127 :: Int8
127
Prelude> 128 :: Int8

<interactive>:11:1: Warning:
    Literal 128 is out of the
      Int8 range -128..127
    If you are trying to write a large
      negative literal,
    use NegativeLiterals
-128
Prelude> (127 + 1) :: Int8
-128

گرامری که اینجا می‌بینید، Int8 ::، وظیفه‌ی تخصیصِ تایپِ Int8 به این اعداد رو داره. همونطور که در فصل بعد می‌بینیم، اعداد در باطن چندریختی اند، و کامپایلر تا زمانی که مجبور نباشه، هیچ تایپِ معینی بهشون اختصاص نمیده. یه کم عجیب و غیرمنتظره میشد اگه پیش‌فرضِ کامپایلر تخصیص تایپِ Int8 به همه‌ی اعداد بود. ما هم به همین خاطر با اون گرامر، تایپِ معینِ Int8 رو به اعدادمون اختصاص دادیم.

همونطور که می‌بینید، ۱۲۷ در بازه‌ی Int8 هست و موردی نداره. ۱۲۸ بعد از یه اخطار سرریز شد، ۱ + ۱۲۷ هم سرریز شد و برگشت به کوچکترین مقدارِ عددی. به خاطر محدود بودنِ حافظه‌ای که مقادیر Int8 می‌تونن اشغال کنن، توانایی تطابق با عددی مثل ۱۲۸ رو هم ندارن (برعکسِ Integer). عدد ۸ نشون دهنده‌ی تعداد بیت‌هایی‌ه که تایپ برای ارائه‌ی اعداد استفاده می‌کنه*. گاهی اوقات ممکنه اندازه‌ی ثابت این تایپ‌ها مفید باشه، ولی عمدتاً Integer ارجح‌ه.

بیشترین و کمترین مقادیرِ تایپ‌های عددی رو می‌تونین با استفاده از minBound و maxBound از تایپکلاسِ Bounded پیدا کنین. در زیر چندتا مثال زدیم:

Prelude> import GHC.Int
Prelude> :t minBound
minBound :: Bounded a => a
Prelude> :t maxBound
maxBound :: Bounded a => a

Prelude> minBound :: Int8
-128
Prelude> minBound :: Int16
-32768
Prelude> minBound :: Int32
-2147483648
Prelude> minBound :: Int64
-9223372036854775808

Prelude> maxBound :: Int8
127
Prelude> maxBound :: Int16
32767
Prelude> maxBoudn :: Int32
2147483647
Prelude> minBound :: Int64
9223372036854775807

اگه تایپی، این تایپکلاسِ بخصوص رو داشته باشه، می‌تونین محدوده‌ی مقادیرِ ممکن اون تایپ رو پیدا کنین. در مثال بالا دیدیم که بازه‌ی مقادیرِ قابل ارائه با Int8 از ۱۲۸- تا ۱۲۷ بود.

با دستورِ info:، می‌تونین همه‌ی تایپکلاس‌های یه تایپ رو تو GHCi ببینین. این دستور، تعریفِ نوع‌داده‌ای هم که استعلام کردین رو نشون میده. برای مثال، می‌خوایم بدونیم که آیا تایپِ Int تایپکلاسِ Bounded رو داره یا نه:

Prelude> :i Int
data Int = GHC.Types.I# GHC.Prim.Int#
-- Defined in ‘GHC.Enum’
instance Bounded Int

البته Int تایپکلاسهای خیلی بیشتری داره که اینجا ننوشتیم.

اعداد کسری

چهار تایپِ رایجِ اعداد Fractional در هسکل Float،Double،Rational، و Scientific اند. Float،Double، و Rational از ابتدا توی GHC هستن. اما Scientific، همونطور که گفتیم از یه کتابخونه میاد. Rational و Scientific هر دو دقتِ نامحدود دارن، ولی Scientificبهینه‌تره. منظور از این نامحدود بودن اینه که از این دو تایپ، بر خلاف Float و Double که به یه دقت مشخص محدودن، میشه برای محاسباتی که نیاز به دقتهای خیلی بالا دارن استفاده کرد. تقریباً هیچ وقت Float لازم نمیشه، مگر برای کارهایی مثل برنامه‌نویسیِ گرافیکی با OpenGL.

بعضی محاسبه‌های عددی با اعداد کسری کار می‌کنن. یه مثالِ خوب، تابع تقسیم (/) ِه، که تایپِ‌ش اینه:

Prelude> :t (/)
(/) :: Fractional a => a -> a -> a

نوشته‌ی <= Fractional a یه محدودیتِ تایپکلاسی ِه. یعنی متغیرِ a مقیّد به داشتن تایپکلاس Fractional ِه. مهم نیست a چه نوع عددی باشه، فقط باید یک نمونه از تایپکلاسِ Fractional داشته باشه؛ به عبارت دیگه، باید یک تعریف وجود داشته باشه که عملیاتِ اون تایپکلاس رو برای اون تایپ توصیف کنه. تابعِ / یک عددی که تایپکلاس Fractional داره رو می‌گیره، به یه عددِ دیگه از همون تایپ تقسیم می‌کنه، و یه مقدار از همون تایپ رو به عنوان جواب برمی‌گردونه.

Fractional تایپکلاسیه که تایپ‌هاش ملزم به داشتنِ یک نمونه از تایپکلاس Num هستن. در چنین رابطه‌ای، میگیم Num یه سوپرکلاس از Fractional ِه. بنابراین، (+) و توابع دیگه از تایپکلاس Num رو میشه با اعداد Fractional استفاده کرد، ولی توابعِ تایپکلاسِ Fractional رو نمیشه با همه‌ی تایپ‌های Num استفاده کرد.

این نتیجه‌ی استفاده از (/) در REPL ِه:

Prelude> 1 / 2
0.5
Prelude> 4 / 2
2.0

دقت کنید با اینکه جوابمون یه عدد کامل بود، باز هم کسری شد. دلیلش اینه که مقادیر Fractional a => a به صورت پیش‌فرض به تایپِ Double تبدیل میشن. اکثر مواقع، بهتره صراحتاً از Double استفاده نکنین، Scientific با دقت نامحدودی که داره (میشه گفت داداشِ Integer ه!)، گزینه‌ی بهتریه. اکثر مردم با محاسباتِ ممیز شناور، به خاطرِ رفتارهای نامتعارف‌ش، راحت کنار نمیان (اون رفتارها به عمد طراحی شدن، ولی اون یه مبحث دیگه‌ست)؛ شما هم برای آسودگیِ خاطر، از تایپ‌های با دقتِ نامحدود استفاده کنین.