۶ - ۸تایپکلاس Ord
حالا به تایپکلاسِ Ord
میپردازیم. قبلاً گفتیم که این تایپکلاس برای تایپِ چیزهاییه که میشه با ترتیب قرار بگیرن. اگه از دستورِ :info
برای Ord
در REPL استفاده کنین، نمونههای خیلی زیادی براش میبینین. ما این لیست رو خلاصه میکنیم و فقط روی چندتا از اونها تمرکز میکنیم، ولی طبق معمول شما رو به گشت و گذارِ بیشتر تشویق میکنیم:
Prelude> :info Ord
class Eq a => Ord a where
compare :: a -> a -> Ordering
(<) :: a -> a -> Bool
(>=) :: a -> a -> Bool
(>) :: a -> a -> Bool
(<=) :: a -> a -> Bool
max :: a -> a -> a
min :: a -> a -> a
instance Ord a => Ord (Maybe a)
instance (Ord a, Ord b) => Ord (Either a b)
instance Ord Integer
instance Ord a => Ord [a]
instance Ord Ordering
instance Ord Int
instance Ord Float
instance Ord Double
instance Ord Char
instance Ord Bool
میبینید که اون بالا یه محدودیتِ تایپکلاسی ِ دیگه داریم. Ord
با Eq
محدود شده چون اگه بخواین مثلاً المانهای یه لیست رو مقایسه و مرتب کنین، یه راهی برای بررسی تساوی ِ اونها لازم دارین. پس Ord
به Eq
و متودهاش احتیاج داره. توابعِ این تایپکلاس با ترتیببندی سروکار دارن. بعضیهاشون خروجیِ Bool
میدن که تا حالا یه کم باهاشون بازی کردیم. ببینیم بعضیهای دیگه چی کار میکنن:
Prelude> compare 7 8
LT
Prelude> compare 4 (-4)
GT
Prelude> compare 4 4
EQ
Prelude> compare "Julie" "Chris"
GT
Prelude> compare True False
GT
Prelude> compare True True
EQ
تابعِ compare
برای همهی تایپهایی که تایپکلاس Ord
دارن کار میکنه، مثل Bool
، اما بر خلاف توابعی مثل <
، >
، >=
، و <=
بجای مقادیرِ Bool
، مقادیرِ Ordering
برمیگردونه.*
م. مقادیرِ تایپِ Ordering
از این قرارند: LT
، EQ
، یا GT
که به ترتیب مخفف کوچکتر از، برابر، و بزرگتر از هستن.
شاید متوجه شده باشین که True
از False
بزرگتره. این به خاطر نحوهی تعریفِ تایپ Bool
ِه: False | True
. اگه بخواین از جنبهی فلسفی بررسی کنین شاید دلایل عمیقتری هم پیدا کنین.
توابعِ min
و max
هم با همهی تایپهایی که تایپکلاسِ Ord
دارن کار میکنن:
Prelude> max 7 8
8
Prelude> min 10 (-10)
-10
Prelude> max (3, 4) (2, 3)
(3,4)
Prelude> min [2, 3, 4, 5] [3, 4, 5, 6]
[2,3,4,5]
Prelude> max "Julie" "Chris"
"Julie"
از روی تایپ سیگنچر ِ این تابعها مشخصه که دو تا پارامتر دارن. اگه بخواین بیشترین یا کمترین بین سه مقدار رو پیدا کنین، میشه دو تا از اون توابع رو تودرتو کرد:
Prelude> max 7 (max 8 9)
9
اگه فقط یه آرگومان بدین، این پیغامِ خطایِ در ظاهر عجیب رو میبینین:
Prelude> max "Julie"
No instance for (Show ([Char] -> [Char]))
-- [1] [2] [ 3 ]
arising from a use of ‘print’
-- [4]
In a stmt of an interactive GHCi command: print it
-- [ 5 ]
۱.
هسکل برای یه مقدار از یه تاپی، یک نمونه ِ تایپکلاسی رو پیدا نکرد.
۲.
نمونه ای که پیدا نکرد از تایپکلاسِ Show
بود (که به GHCi اجازهی چاپ میده). این تایپکلاس رو جلوتر میبینیم.
۳.
برای تایپِ String -> String
یه نمونه از Show
پیدا نکرد. به عنوانِ یه قاعدهی کلی، هیچ چیز با تایپِ (->)
نباید از Show
نمونه داشته باشه چون (->)
یه تابع رو نشون میده و نه یه مقدارِ ثابت.
۴.
چون (به طور غیرمستقیم) تابعِ print
رو صدا زدیم، یه نمونه از Show
لازم داشتیم. به محدودیت تایپکلاسی ِ Show
در تایپش دقت کنین: print :: Show a => a -> IO ()
۵.
دستورِ print it
از GHCi، از طرف ما تابعِ print
رو صدا زد.
هر وقت از GHCi درخواستِ چاپ به ترمینال میکنیم، در واقع داریم غیرمستقیم تابعِ print
با تایپِ Show a => a -> IO ()
رو صدا میزنیم. آرگومانِ اولِ print
باید یه نمونه از Show
داشته باشه. اون پیغامِ خطا به خاطر کم بودنِ آرگومانهای max
بود، یعنی باید یه String
ِ دیگه بهش بدیم تا یه String
(یا همون [Char]
) رو برگردونه که تایپی با تایپکلاسِ Show
ِه. تا قبل از اعمالِ این آرگومانِ دوم، هنوز یه تابعه، و یه تابع، نمونه ای از Show
نداره. علت اون خطا درخواستِ چاپ ِ یه تابع بجای یه مقدار بود.
نمونههای Ord
همینطور که پیش میریم مثالهای بیشتری از نمونه نوشتن میبینیم و با جزئیاتِ بیشتری نحوهی تعریفِ نوعدادههایِ خودتون رو توضیح میدیم. قبلتر چندتا نمونه برای Eq
نوشتیم. حالا با نوشتن نمونههای Ord
مهارتِ نمونهنویسی (که از مهارتهای واجب در هسکله) رو در خودمون تقویت میکنیم.
وقتی نمونههای تایپکلاسِ Ord
رو برای یه تایپ مشتق میگیرین، نتیجه براساسِ نحوهی تعریفِ نوعداده تعیین میشه، ولی اگه نمونه ِ دلخواهِ خودتون رو بنویسین، هر رفتاری که بخواین تعریف میکنین. دوباره از روزهای هفته استفاده میکنیم:
data DayOfWeek =
Mon | Tue | Weds | Thu | Fri | Sat | Sun
deriving (Ord, Show)
فقط Ord
و Show
رو مشتق گرفتیم چون باید هنوز نمونه ِ Eq
که برای این نوعداده نوشتیم رو در گستره داشته باشین. اگه ندارین، دو چاره دارین: یکی اینکه بیارینش توی این فایلی که الان دارین مینویسین، یا Eq
رو داخلِ پرانتز تایپکلاسهایی که مشتق گرفتین اضافه کنین. بدونِ Eq
نمیشه تایپکلاسِ Ord
داشت. پس اگه یه کدوم از اون کارها رو انجام ندین (فقط یکی، نه هر دو) کامپایلر بهِتون گیر میده.
مقادیر سمت چپ کوچکتر از مقادیر سمت راست به حساب میان؛ شبیه این میمونه که روی یه نمودارِ اعداد نوشته شده باشن:
Prelude> Mon > Tue
False
Prelude> Sun > Mon
True
Prelude> compare Tue Weds
LT
ولی اگه بخوایم بگیم جمعه همیشه بهترین روز هفتهست، میتونیم خودمون نمونه ِ Ord
رو بنویسیم:
data DayOfWeek =
Mon | Tue | Weds | Thu | Fri | Sat | Sun
deriving (Eq, Show)
instance Ord DayOfWeek where
compare Fri Fri = EQ
compare Fri _ = GT
compare _ Fri = LT
compare _ _ = EQ
حالا اگه جمعه رو با بقیهی روزها مقایسه کنیم، همیشه بزرگتره. دقت کنین که ارزش بقیهی روزها یکسانه:
Prelude> compare Fri Sat
GT
Prelude> compare Sat Mon
EQ
Prelude> compare Fri Mon
GT
Prelude> compare Sat Fri
LT
Prelude> Mon > Fri
False
Prelude> Fri > Sat
True
ولی تایپکلاسِ Eq
رو بالا مشتق گرفتیم، پس رفتارِ تساوی همونطور که انتظار داریم میشه:
Prelude> Sat == Mon
False
Prelude> Fri == Fri
True
موقعِ نوشتن نمونههای Ord
به چندتا چیز دقت کنین: اول اینکه هماهنگیِ Ord
با نمونه ِ Eq
(چه مشتق گرفته شده باشه، چه خودتون نوشته باشین) کار عاقلانهایه. اگه x == y
، اون موقع باید جوابِ compare x y
هم EQ
باشه. دیگه اینکه نمونهتون یه ترتیبِ معقول از مقادیر رو توصیف کنه. یعنی همهی حالتها رو در نظر بگیرین و نمونه ِ ناقص ننویسین (همونطور که سَرِ Eq
توضیح دادیم). در کل، نمونه ِ Ord
ِتون باید طوری نوشته بشه که اگه compare x y
جواب LT
میده، compare y x
خروجی GT
بده.
Ord
تایپکلاسِ Eq
رو نتیجه میده
به دلایلی که قبلاً گفتیم، کدِ زیر تایپچک نمیشه:
check' :: a -> a -> Bool
check' a a' = a == a'
پیغام خطا میگه Eq
لازم داریم، که با عقل جور در میاد!
No instance for (Eq a) arising from a use of ‘==’
Possible fix:
add (Eq a) to the context of
the type signature for check' :: a -> a -> Bool
In the expression: a == a'
In an equation for ‘check'’: check' a a' = a == a'
چی میشه اگه بجای Eq
که خواسته، Ord
اضافه کنیم؟
check' :: Ord a => a -> a -> Bool
check' a a' = a == a'
کامپایل میشه. GHC تایپکلاسِ Ord
نخواسته بود، پس چرا کار کرد؟ چون طبق تعریف، هر چیزی که یه نمونه از Ord
داره، باید یه نمونه از Eq
هم داشته باشه. از کجا میدونیم؟ بالاتر دلیل مفهومیش رو گفتیم – که برای مرتب کردنِ مقادیر، بررسیِ تساویشون لازمه. اما میتونیم نتیجهی :info Ord
توی GHCi هم ببینیم:
Prelude> :info Ord
class Eq a => Ord a where
... با یه مشت سروصدا ...
تعریفِ کلاس Ord
میگه هر a
ای که بخواد یه نمونه از Ord
تعریف کنه، حتماً باید یه نمونه از Eq
هم داشته باشه. در واقع Eq
یه اَبرکلاس برای Ord
ِه.
معمولاً اعمالِ محدودیتهای حداقل و لازم برای توابع مطلوبتره – یعنی اگه مثال بالا یه کدِ واقعی بود از Eq
استفاده میکردیم – ولی این کار رو کردیم تا شما ایدهی بهتری از محدودیتهای تایپکلاسی و سوپرکلاسها در هسکل به دست بیارین.
تمرینها: کار میکنه؟
بگین کُدهای زیر کار میکنن یا نه، اگه کار میکنن چه جوابی میدن، و اگه کار نمیکنن دلیلشون چیه (طبق معمول جوابهاتون رو تو REPL تست کنین):
۱.
max (length [1, 2, 3])
(length [8, 9, 10, 11, 12])
۲.
compare (3 * 4) (3 * 5)
۳.
compare "Julie" True
۴.
(5 + 3) > (3 + 6)