۲ - ۸توابع عددی در هسکل
تو این بخش به بررسی چندتا از توابع و عملگرهای پایهایِ حساب میپردازیم، که از این قرارند:
| عملگر | اسم | کاربرد |
|---|---|---|
+ | به علاوه | جمع |
- | منها | تفریق |
* | ستاره | ضرب |
/ | خط مورب | تقسیم کسری |
div | تقسیم | تقسیم صحیح، گِرد به پایین |
mod | پیمانه | باقیمانده از تقسیم پیمانهای |
quot | خارجقسمت | تقسیم صحیح، گِرد به سمت صفر |
rem | باقیمانده | باقیمانده از تقسیم |
احتمالاً براتون واضحه، اما منظور از تقسیمِ صحیح، تقسیمِ اعدادِ integer یا صحیح ِه. اون توابعْ اعدادِ کسری نمیگیرن و فقط اعداد integer میگیرن. به همین خاطر هم جوابشون گِرد میشه.
در زیر از هر کدوم یه مثال تو REPL زدیم:
Prelude> 1 + 1
2
Prelude> 1 - 1
0
Prelude> 1 * 1
1
Prelude> 1 / 1
1.0
Prelude> div 1 1
1
Prelude> mod 1 1
0
Prelude> quot 1 1
1
Prelude> rem 1 1
0به خاطرِ طریقهی گرد کردنِ div و quot، کاربردِ div برای تقسیمِ صحیح بیشتره:
-- به پایین گرد میشه
Prelude> div 20 (-6)
-4
-- به سمت صفر گرد میشه
Prelude> quot 20 (-6)
-3کاربرد rem و mod هم یه کم با هم فرق دارن؛ تو این فصل mod رو با جزئیاتِ بیشتری بررسی میکنیم. تقسیم کسری (/) رو تو یه فصل جلوتر توضیح میدیم، چون قبلش باید کمی با تایپها و تایپکلاسها آشنایی پیدا کنیم.
قوانین خارجقسمت و باقیمانده
در برنامهنویسی، اکثراً توابعی که برای تقسیم و باقیمانده وجود دارن بیشتر از ریاضیه، و برای درک بهتر، آشنایی با قوانین مرتبط با quot و rem، و div و mod خیلی کمک میکنه.* اینجا یه نگاه بهشون میندازیم.
(quot x y)*y + (rem x y) == x
(div x y)*y + (mod x y) == xگرفته شده از بلاگ لنارت آگوستسون، یا جواب سؤال در سایت Stack Overflow.
مراحل اثبات این دو تا رو اینجا نمینویسیم، ولی میتونیم با مثال تا حدی مفهومشون رو نشون بدیم:
(quot x y)*y + (rem x y) == x
(-۴) برابر y برابر ۱۰ و x با فرض
(quot 10 (-4))*(-4) + (rem 10 (-4))
quot 10 (-4) == (-2) و rem 10 (-4) == 2
(-2)*(-4) + (2) == 10
10 == xبه جوابی که میخواستیم رسیدیم.
حالا برای div و mod:
(div x y)*y + (mod x y) == x
(-۴) برابر y برابر ۱۰ و x با فرض
(div 10 (-4))*(-4) + (mod 10 (-4))
div 10 (-4) == (-3) و mod 10 (-4) == -2
(-3)*(-4) + (-2) == 10
10 == xبه نتیجه میرسیم که در دنیای تقسیم اعداد صحیح، همه چیز خوب و خوشه.
استفاده از mod
ما اینجا به توضیح کامل حساب پیمانهای نمیپردازیم، ولی میخوایم یه درکِ کلی از mod به کسانی که باهاش آشنایی ندارن بدیم. و اینکه دقیقاً تو هسکل چطوری کار میکنه.
بالاتر توی جدول گفتیم که mod باقیماندهی تقسیم ماژولار رو میده. بدون آشنایی با تقسیم ماژولار، تفاوتِ پرفایدهی بین mod و rem خیلی واضح نمیشه.
حساب پیمانهای یک سیستمی از محاسبات اعداد صحیحه که اعداد بعد از رسیدن به یه مقدار معلوم (به اسم مدول)، "دور میزنن" به مقدار اولیه. مثال رایج برای توضیح این سیستم، ساعته.
وقتی زمان رو بر حسب یه ساعت ۱۲-ساعته میشماریم، بعد از ساعت ۱۲ باید شمارشمون رو از اول شروع کنیم. برای مثال اگه الان ساعت ۸:۰۰ باشه، و بخوایم بدونیم ۸ ساعت دیگه ساعت چند میشه، نمیگیم ۸ + ۸، پس ساعت میشه ۱۶*.
به جای اون کار، هر ۱۲ ساعت شمارش رو از اول شروع میکنیم. پس اگه بخوایم ۸ ساعت به ۸:۰۰ اضافه کنیم، اول ۴ ساعت اضافه میکنیم تا به ۱۲ برسیم، و بعد از اول شروع میکنیم، انگار ۱۲ همون صِفره، و ۴ ساعت باقیمانده از ۸ ساعتمون رو اضافه میکنیم تا به ساعت ۴:۰۰ برسیم. بنابراین، ۸ ساعت بعد از ۸:۰۰، ساعت میشه ۴:۰۰.
البته در ساعتهای ۲۴-ساعته، ساعت ۱۶:۰۰ درسته و معنی میده؛ ولی اگه ۸ ساعت بعد از ۸:۰۰ بعدازظهر رو بخوایم، جواب ۱۶:۰۰ صبح نمیشه. مدول در ساعتهای ۲۴-ساعته با ساعتهای ۱۲-ساعته متفاوته.
به چنین چیزی میگیم پیمانه ِ ۱۲. در یک ساعتِ ۱۲-ساعته، ۱۲ با خودش و صفر برابره، و به نوعی ساعت ۱۲:۰۰ ساعت ۰:۰۰ هم هست. منظور از پیمانه ِ ۱۲ اینه که ۱۲، هم ۱۲ ِه، و هم صفر.
برای چنین کاربردی، بیشترِ مواقع mod و rem یه جواب میدن:
Prelude> mod 15 12
3
Prelude> rem 15 12
3
Prelude> mod 21 12
9
Prelude> rem 21 12
9
Prelude> mod 3 12
3
Prelude> rem 3 12
3دو تا آخری شاید عجیب باشن... rem و mod فقط با تقسیم صحیح کار میکنن، پس جوابِ تقسیم با یه عدد بزرگتر، مساوی میشه با صفر، به اضافهی باقیماندهای که برابر با همون عددِ کوچکتر (صورت کسر یا مقسوم) هست. هر وقت بخوایم از تقسیم با یه عددِ بزرگتر جواب کسری بگیریم، از (/) استفاده میکنیم و دیگه باقیمانده هم نداریم.
فرض کنید یه تابع بخوایم که به ما بگه چند روز بعد یا قبل از امروز، چه روزی از هفته میشه. اول باید هر روزِ هفته رو با یه عدد معادل کنیم، پس شنبه رو با صفر نشون میدیم*. اگه امروز یکشنبه باشه، و بخوایم بدونیم ۲۳ روز دیگه چند شنبهست، چنین کاری جواب میده:
Prelude> mod (6 + 5) 7
4عدد ۱ برای یکشنبهست (روز مبدأ)، ۲۳ هم تعداد روزیه که میخوایم اضافه کنیم. وقتی با mod ِعدد ۷ حساب کنیم، عددی رو برمیگردونه که نشون دهندهی روز مقصده (طبق شمارهگذاری خودمون).
ممکنه براتون عادیتر باشه که روزهای هفته رو از ۱ تا ۷ بشمرین، ولی برنامهنویسها دوست دارن از صفر بشمارن.
پنج روز بعد از جمعه هم میشه چهارشنبه:
Prelude> mod (6 + 5) 7
4اگه از rem استفاده کنیم، جوابی میگیریم که ظاهراً یکسانه:
Prelude> rem (1 + 23) 7
3ولی اگه بخوایم بدونیم چند روز قبل چند شنبه بوده، جواب mod و rem فرق میکنن. ببینیم اگه امروز سهشنبه باشه، ۱۲ روز قبلش چند شنبه بوده:
Prelude> mod (3 - 12) 7
5
Prelude> rem (3 - 12) 7
-2جواب mod درسته، ولی rem جوابی که میخوایم رو نمیده.
فرق کلیدیِ بین این دو تابع در هسکل (نه همهی زبانها) اینه که جوابِ mod همیشه با مقسومعلیه (مخرج کسر) همعلامته، ولی rem همیشه با مقسوم (صورت کسر) علامتِ یکسان داره:
Prelude> (-5) `mod` 2
1
Prelude> 5 `mod` (-2)
-1
Prelude> (-5) `mod` (-2)
-1ولی:
Prelude> (-5) `rem` 2
-1
Prelude> 5 `rem` (-2)
1
Prelude> (-5) `rem` (-2)
-1با کمی تجربه میشه تشخیص داد باید کجا از کدومشون استفاده کرد.
اعداد منفی
به خاطر تعامل پرانتزها، currying، و گرامر ِ میانوندی در هسکل، اعداد منفی شرایط خاصی پیدا میکنن.
وقتی فقط یه عدد منفی به تنهایی میخواین، کد زیر موردی نداره:
Prelude> -1000
-1000ولی بعضی مواقع جواب نمیده:
Prelude> 1000 + -9
<interactive>:3:1
Precedence parsing error
cannot mix ‘+’ [infixl 6] and
prefix `-` [infixl 6]
in the same infix expressionخوشبختانه قبل از اجرای کد، خطا مون پیدا شد. دقت کنین که پیغام خطا از تقدم ایراد گرفته. جمع و تفریق هر دوشون تقدم ِ یکسان (۶) دارن، و GHCi هم فکر میکنه که ما میخواستیم جمع کنیم بعد تفریق، نه اینکه با یه عددِ منفی جمع کنیم، به همین خاطر نمیدونه چطور تلاقیِ تقدمها رو حل کنه و جواب بده. یه تغییراتی لازمه تا بتونیم اعداد مثبت و منفی رو با هم جمع کنیم:
Prelude> 1000 + (-9)
991منفی کردن اعداد با یک مِنها (-) در هسکل، نوعی شکرِ گرامری ِه. syntaxگرامر، ساختارِ نوشتاریه که باهاش برنامه رو بیان میکنیم؛ و شکرِ گرامری راهی برای سادهتر کردن این متنها برای خوندن و نوشتنه. شکر گرامری هیچ تأثیری در معنا و مفهوم برنامهها نداره، فقط خوندن یا نوشتن کد رو راحتتر میکنه، روش حل مسائل با کد رو هم تغییری نمیده. معمولاً اگه در کُدی که به REPL یا کامپایلر میدیم از این نوع شکرها وجود داشته باشه، بعد از پارس شدن، یه تبدیلِ جزئی از فرم کوتاه (یا "شیرینتر") به فرم گویاتر روی کد انجام میشه.
در مورد این شکر گرامری، منها میتونه دو معنی داشته باشه: یکی اینکه مستعاری از تابعِ negate باشه، یا اینکه تابعِ تفریق باشه. در زیر، هر دو مثال دقیقاً معادلاند، چون - به negate ترجمه میشه:
Prelude> 2000 + (-1234)
766
Prelude> 2000 + (negate 1234)
766ولی اینجا - برای تفریق استفاده شده:
Prelude> 2000 - 1234
766خوشبختانه تو هسکل، گرامرِ سربار مثل این زیاد نیست.