۲ - ۸توابع عددی در هسکل
تو این بخش به بررسی چندتا از توابع و عملگرهای پایهایِ حساب میپردازیم، که از این قرارند:
عملگر | اسم | کاربرد |
---|---|---|
+ | به علاوه | جمع |
- | منها | تفریق |
* | ستاره | ضرب |
/ | خط مورب | تقسیم کسری |
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
خوشبختانه تو هسکل، گرامرِ سربار مثل این زیاد نیست.