۴ - ۴تایپهای عددی
اعداد رو کمی در فصلهای گذشته دیدیم، حالا اینجا با جزئیات بیشتری بررسیشون میکنیم. مهمه که بدونیم هسکل فقط از یک تایپ برای اعداد استفاده نمیکنه. تایپِ عددهایی که بیشتر باهاشون سر و کار داریم، اینها هستن:
اعدادِ صحیح
اعدادِ تام یا کاملاند، چه مثبت و چه منفی.
۱.
تایپِ Int: این تایپ، عددِ صحیح با دقتِ ثابت ِه. منظور از دقتِ ثابت اینه که محدوده دارن، با یه مقدارِ حداکثر و یه مقدارِ حداقل؛ و نمیتونن هر چقدر که میخوایم بزرگ یا کوچک باشن – کمی جلوتر بیشتر توضیح میدیم.
۲.
تایپِ Integer: این تایپ هم برای اعدادِ صحیح ِه، ولی اعدادش محدودهای ندارن (هر چقدر که بخوایم بزرگ یا کوچک میشن).
اعدادِ کسری
این اعداد صحیح نیستن. مقادیرِ Fractional شامل این چهار تایپاند:
۱.
تایپِ Float: این تایپ برای اعداد با ممیزِ شناور ِ تک-دقتی به کار میره. اعدادِ ممیز-ثابت، تعدادِ ثابتی از ارقام برای قبل و بعد از ممیز دارن. در مقابل، ممیز شناور میتونه تعداد بیتهایی که برای اعداد قبل و بعد از ممیز استفاده میشن رو جابجا کنه. البته این انعطافپذیری منجر به نقضِ بعضی فرضیههای مشترک برای اعداد میشه، و بهتره با نهایتِ دقت از اونها استفاده بشه. به طور کلی، در کاربردهای تجاری اصلاً نباید از اعداد با ممیز شناور استفاده کرد.
۲.
تایپِ Double: تایپی برای اعداد با ممیز شناور ِ دو-دقتی. مشابهِ تایپِ Float، با تفاوت اینکه دو برابر بیت برای توصیفِ اعداد دارن.
۳.
تایپِ Rational: نوعی عددِ کسری که نسبت ِ دو عددِ صحیح رو نشون میده. مقدارِ 5 / 7 :: Rational حامِل دو مقدارِ 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اما اینها integral نیستند:
1.3 1/2Integer
اینها همون اعدادِ کاملیاند که بهشون عادت داریم. میتونن مثبت یا منفی باشن، و هر چقدر هم لازم باشه بزرگ یا کوچک میشن.
تایپِ 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 ارجحه.
تایپهای Int که اندازهی ثابت هستن، با رَوِشِ مکملِ دو اعدادشون رو ارائه میدن.
بیشترین و کمترین ِ مقادیرِ تایپهای عددی رو میتونین با استفاده از 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> maxBound :: 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 ِه!)، گزینهی بهتریه. اکثر مردم با محاسباتِ ممیز شناور، به خاطرِ رفتارهای نامتعارفش، راحت کنار نمیان (اون رفتارها به عمد طراحی شدن، ولی اون یه مبحثِ دیگهست)؛ شما هم برای آسودگیِ خاطر، از تایپهای با دقتِ نامحدود استفاده کنین.