۶ - ۱۲نمونهها براساس تایپها خبر میشن
بدون اینکه توضیح بدیم، چند بار گفتیم که تایپکلاسها براساس تایپها خبر میشن، و درکِ این موضوع مهمه. تایپکلاسها با مجموعهی عملیاتها و مقادیری که به همراه همهی نمونهها ارائه میشن تعریف میشن. نمونههای تایپکلاسها، جفتهای یکتایی از تایپکلاس با یه تایپاند؛ و راههای پیادهسازی ِ متودهای تایپکلاس رو برای اون تایپ تعریف میکنن.
با یه کم کُد مفهومِ اینها رو نشون میدیم. اول از همه، فقط برای توضیح بهتر، تایپکلاس و نمونههای خودِمون رو نوشتیم. چنین جزئیاتی تو این مثال حائز اهمیت نیستن، فقط اینها رو به خاطر داشته باشین:
یه تایپکلاس مجموعهای از توابع و یا مقادیر رو تعریف میکنه؛
تایپها نمونههایی از اون تایپکلاس دارن؛
نمونهها راههای استفاده از توابعِ تایپکلاس رو برای اون تایپ مشخص میکنن.
این تایپکلاس فقط برای نمایشه و خیلی احمقانهست. لطفاً تایپکلاسهای این شکلی ننویسین:
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
یه کم... دلبخواهی و شکمیه. تو هسکل راههای بهتری برای بیان کاراییش وجود داره. اینجا توابع و مقادیر به تنهایی کفایت میکنند.