۶ - ۱۲نمونهها براساس تایپها خبر میشن
بدون اینکه توضیح بدیم، چند بار گفتیم که تایپکلاسها براساس تایپها خبر میشن، و درکِ این موضوع مهمه. تایپکلاسها با مجموعهی عملیاتها و مقادیری که به همراه همهی نمونهها ارائه میشن تعریف میشن. نمونههای تایپکلاسها، جفتهای یکتایی از تایپکلاس با یه تایپاند؛ و راههای پیادهسازی ِ متودهای تایپکلاس رو برای اون تایپ تعریف میکنن.
با یه کم کُد مفهومِ اینها رو نشون میدیم. اول از همه، فقط برای توضیح بهتر، تایپکلاس و نمونههای خودِمون رو نوشتیم. چنین جزئیاتی تو این مثال حائز اهمیت نیستن، فقط اینها رو به خاطر داشته باشین:
یه تایپکلاس مجموعهای از توابع و یا مقادیر رو تعریف میکنه؛
تایپها نمونههایی از اون تایپکلاس دارن؛
نمونهها راههای استفاده از توابعِ تایپکلاس رو برای اون تایپ مشخص میکنن.
این تایپکلاس فقط برای نمایشه و خیلی احمقانهست. لطفاً تایپکلاسهای این شکلی ننویسین:
class Numberish a where
fromNumber :: Integer -> a
toNumber :: a -> Integer
-- ببینین data رو به چشمِ همون newtype فعلاً
newtype Age =
Age Integer
deriving (Eq, Show)
instance Numberish Age where
fromNumber n = Age n
toNumber (Age n) = n
newtype Year =
Year Integer
deriving (Eq, Show)
instance Numberish Year where
fromNumber n = Year n
toNumber (Year n) = nحالا فرض کنین با استفاده از این تایپکلاس و دو تایپ و نمونهها یه تابع بنویسیم:
sumNumberish :: Numberish a => a -> a -> a
sumNumberish a a' = fromNumber summed
where integerOfA = toNumber a
integerOfAPrime = toNumber a'
summed =
integerOfA + integerOfAPrimeحالا یه لحظه تأمل کنیم. در تعریف تایپکلاسِ Numberish فقط تایپها تعریف شدن، هیچ جمله یا کُدی که بشه کامپایل و اجرا کرد توصیف نشده. کُد توی نمونههای Age و Year تعریف شده. حالا هسکل چطور میدونه کجا دنبال کُد بگرده؟
Prelude> sumNumberish (Age 10) (Age 10)
Age 20اینجا دید که آرگومانهای sumNumberish از تایپِ Age بودن و از نمونه ِ Numberish برای Age استفاده کرد. با استنتاج تایپ هم میشه این رو دید:
Prelude> :t sumNumberish
sumNumberish :: Numberish a => a -> a -> a
Prelude> :t sumNumberish (Age 10)
sumNumberish (Age 10) :: Age -> Ageبعد از اعمالِ اولین پارامتر به یه مقدار با تایپ Age، هسکل میدونه که همهی تایپهای Numberish a => a ِ دیگه هم باید Age باشن.
حالا تایپکلاس و نمونههاش رو تغییر میدیم تا حالتی رو ببینیم که هسکل اطلاعاتِ کافی برای شناساییِ تایپِ معین (و متعاقباً نمونهای که کُد رو از روش بخونه) رو نداره.
(این از قبلی هم بدتره. اصلاً از تایپکلاسها برای تعریفِ مقادیرِ پیشفرض استفاده نکنین. جدی جدی. وگرنه نینجاهای هسکل پیداتون میکنن و تو خمیردندونتون دوغاب میریزن.)
class Numberish a where
fromNumber :: Integer -> a
toNumber :: a -> Integer
defaultNumber :: a
instance Numberish Age where
fromNumber n = Age n
toNumber (Age n) = n
defaultNumber = Age 65
instance Numberish Year where
fromNumber n = Year n
toNumber (Year n) = n
defaultNumber = Year 1988حالا تو REPL میشه دید که گاهی اوقات هیچ راهی برای هسکل وجود نداره که بفهمه چی میخوایم!
Prelude> defaultNumber
No instance for (Show a0) arising
from a use of ‘print’
The type variable ‘a0’ is ambiguous
-- .مبهمه ‘a0’ م. متغیرِ تایپی
Note: There are several potential instances
instance Show a => Show (Maybe a)
instance Show Ordering
instance Show Integer
...به اضافهی ۲۴ تای دیگهدلیل این خطا این بود که هسکل هیچ ایدهای از اینکه تایپِ defaultNumber چی بود، به غیر از اینکه کُدش در نمونههای تایپکلاس Numberish تعریف شدن، نداشت. ولی با اینکه یه مقداره و هیچ آرگومانی نمیگیره یه راهی هست که منظورمون رو به هسکل برسونیم:
Prelude> defaultNumber :: Age
Age 65
Prelude> defaultNumber :: Year
Year 1988تایپی که انتظار دارین رو مشخص کنین و همه چیز خوب میشه! اینجا هسکل با استفاده از اون تعیینِ تایپ، تشخیص میده که ما defaultNumber رو از کدوم یکی نمونههای تایپکلاس میخواستیم تا خبر ِش کنه.
چرا اینطوری تایپکلاس ننویسیم؟
به خاطر دلایلی که سَرِ مانویدها توضیح میدیم، قانونمند بودنِ تایپکلاسهاتون مهمه؛ یعنی مهمه که تایپکلاسهاتون قواعد و قانونهایی برای طرزِ کارشون داشته باشن. Numberish یه کم... دلبخواهی و شکمیه. تو هسکل راههای بهتری برای بیان کاراییش وجود داره. اینجا توابع و مقادیر به تنهایی کفایت میکنند.