۶ - ۷تایپکلاس‌های با تایپ پیش‌فرض

وقتی یه مقدارِ پلی‌مورفیک با تایپکلاسِ محدود (یا اد-هاک) دارین که نیاز هست محاسبه بشه، باید پلی‌مورفیسم ِش حل و یه تایپِ معین براش مشخص بشه. اون تایپِ معین باید از همه‌ی تایپکلاس‌های مورد نیاز، نمونه داشته باشه (مثلاً اگه لازمه هم ‏‎Num‎‏ و هم ‏‎Fractional‎‏ داشته باشه، نمی‌تونه ‏‎Int‎‏ باشه). معمولاً تایپ‌های معین یا در تایپ سیگنچر یا با استنتاج تایپ مشخص میشن، مثل وقتهایی که تایپ یه بیانیه با ‏‎Num a => a‎‏ اعلام شده و با گرفتنِ یه ‏‎Integer‎‏، اون مقدارِ عددیِ پلی‌مورفیک به ‏‎Integer‎‏ تعیین میشه. ولی گاهی اوقات، علی‌الخصوص وقتهایی که در GHCi REPL کار می‌کنین، پیش میاد که تایپِ معیّن‌ای برای یه مقدار پلی‌مورفیک تعیین نکردین. در اینجور مواقع، تایپکلاس به یه تایپِ پیش‌فرض تعیین میشه، و این تایپ‌های پیش‌فرض، از قبل در کتابخونه‌ها مشخص شدن.

وقتی این رو تو REPL می‌نویسیم:

Prelude> 1 / 2
0.5

جوابِ ‏‎0.5‎‏ اینطوری شد چون تایپ‌ش به طور پیش‌فرض به ‏‎Double‎‏ تعیین میشه. میشه با گرامر ِ ‏‎::‎‏ یه تایپِ معین‌تر تعریف کنیم و اون تایپِ پیش‌فرض ِ ‏‎Double‎‏ رو دور بزنیم:

Prelude> 1 / 2 :: Float
0.5
Prelude> 1 / 2 :: Double
0.5
Prelude> 1 / 2 :: Rational
1 % 2

گزارشِ هسکل* تایپ‌های پیش‌فرض ِ زیر رو برای محاسبات عددی مشخص می‌کنه:

default Num Integer
default Real Integer
default Enum Integer
default Integral Integer
default Fractional Double
default RealFrac Double
default Floating Double
default RealFloat Double
*

گزارشِ هسکل استانداردهایی رو برای خود زبان و کتابخونه‌های استاندارد مشخص می‌کنه. جدیدترین نسخه تا این لحظه، گزارش هسکل ۲۰۱۰ هست که می‌تونین از اینجا بگیرین.

تو لیستِ بالا، ‏‎Num‎‏، ‏‎Real‎‏، و غیره تایپکلاس‌ها اند، و ‏‎Integer‎‏ و ‏‎Double‎‏ تایپ‌های پیش‌فرض‌ای هستن که اون تایپکلاس‌ها بهشون تعیین میشن. این تعیین شدن به تایپ‌های پیش‌فرض (م. یا خلاصه‌تر بگیم، type defaulting) برای ‏‎Fractional‎‏، باعث میشه در صورتِ تعیین نکردنِ تایپِ تابعِ ‏‎(/)‎‏، تایپ‌ش از:

(/) :: Fractional a => a -> a -> a

به:

(/) :: Double -> Double -> Double

تغییر کنه. یه مثالِ مشابه برای ‏‎Integral‎‏:

div :: Integral a => a -> a -> a

پیش‌فرض میشه به:

div :: Integer -> Integer -> Integer

وقتی تایپ معین میشه، دیگه محدودیت تایپکلاسی اضافه‌ست. از طرف دیگه، باید تایپکلاس‌هایی که از متغیرهای تایپ‌تون لازم دارین مشخص کنین. اگه امکان نتیجه‌گیری یه تایپِ معین نبود، و هیچ قاعده‌ای برای تایپ‌های پیش‌فرض وجود نداشت، استفاده از مقادیرِ پلی‌مورفیک باعثِ گله کردنِ GHCi به خاطر مبهم بودنِ تایپ میشد.

مثال‌های زیر کار می‌کنن چون همه‌ی این تایپ‌ها تایپکلاسِ ‏‎Num‎‏ دارن:

Prelude> let x = 5 + 5 :: Int
Prelude> x
10

Prelude> let x = 5 + 5 :: Integer
Prelude> x
10

Prelude> let x = 5 + 5 :: Float
Prelude> x
10.0

Prelude> let x = 5 + 5 :: Double
Prelude> x
10.0

می‌تونیم این تایپ رو معین‌تر کنیم، و فرایند کار هم تغییر چندانی نمی‌کنه. در این مورد، از ‏‎Integer‎‏ که تایپکلاس ‏‎Num‎‏ رو داره استفاده می‌کنیم:

let x = 10 :: Integer

let y = 5 :: Integer

-- اینها تایپ‌های تعریف شده‌ی این
-- توابع‌اند، دلیل‌ش هم اینه که این
-- .اومدن Num توابع از تایپکلاسِ
(+) :: Num a => a -> a -> a
(*) :: Num a => a -> a -> a
(-) :: Num a => a -> a -> a

هر تابعی از ‏‎Num‎‏ رو به ‏‎x‎‏ یا ‏‎y‎‏ اعمال کنیم، تایپ‌ش خودبه‌خود به ‏‎Integer‎‏ اختصاصی میشه:

Prelude> :t (x+)
(x+) :: Integer -> Integer

-- برای
(+)   :: Num a => a -> a -> a
-- ه ِInteger تایپ‌ش a وقتی
(+)   :: Integer -> Integer -> Integer
-- اعمال آرگومان اول
(x+)  ::            Integer -> Integer
-- اعمال دومین/آخرین آرگومان
(x+y) ::                       Integer
-- شد Integer جواب نهایی هم

با توابعِ عمومی‌تر (پلی‌مورفیک) میشه توابعِ بخصوص‌تر (مونومورفیک) رو تعریف کرد:

let add = (+) :: Integer -> Integer -> Integer

این کار در خلاف جهت ممکن نیست، چرا که بعد از اختصاصی کردن به ‏‎Integer‎‏، دیگه عمومیتِ ‏‎Num‎‏ رو از دست میدیم:

Prelude> :t id
id :: a -> a
Prelude> let numId = id :: Num a => a -> a
Prelude> let intId = numId :: Integer -> Integer
Prelude> let altNumId = intId :: Num a => a -> a

Could not deduce (a1 ~ Integer)
from the context (Num a)
  bound by the inferred type of 
      altNumId :: Num a => a -> a

or from (Num a1)
  bound by an expression type signature:
    Num a1 => a1 -> a1

‘a1’ is a rigid type variable bound by
  an expression type signature:
    Num a1 => a1 -> a1

Expected type: a1 -> a1
  Actual type: Integer -> Integer

In an equation for ‘altNumId’:
  altNumId = intId :: Num a => a -> a

تایپ مورد انتظار و تایپ واقعی همخونی ندارن. یادتون باشه، تایپ واقعی تایپی‌ه که ما تأمین کردیم؛ تایپ مورد انتظار تایپی‌ه که کامپایلر انتظار داره. اینجا تایپ واقعی معین‌تر از تایپ مورد انتظار ِه. تایپ‌ها رو میشه معین‌تر کرد، ولی نمیشه جامع‌تر یا پلی‌مورفیک‌تر کرد.