۵ - ۶استنتاج نوع

هسکل ما رو مجبور به نوشتنِ تایپ برای هر بیانیه‌ای که می‌نویسیم نمی‌کنه، چون قابلیتِ استنتاج تایپ داره. استنتاجِ تایپ یه الگوریتم برای تشخیص تایپِ بیانیه‌هاست. استنتاج تایپ ِ هسکل بر روی نسخه‌ی پیشرفته‌تری از تایپ سیستمِ ِ داماس-هیندلی-میلنر ساخته شده.

هسکل جامع‌ترین (پلی‌مورفیک ترین) تایپِ ممکن رو نتیجه می‌گیره. اساسِ کار اینه که کامپایلر از مقادیری که تایپ‌شون رو میدونه شروع می‌کنه و تایپِ بقیه‌ی مقادیر رو پیدا می‌کنه. تجربه‌تون که با هسکل بیشتر میشه، فایده‌ی این رو وقتی لمس می‌کنین که می‌خواین کُدِ جدید بنویسین. وقتی برنامه‌تون کامل شه، حتماً تایپ همه‌ی تابع‌ها رو می‌دونین، و عادتِ خوبیه که صراحتاً تایپ‌شون رو بنویسین. یادتون هست یه تایپ سیستم ِ خوب رو به یه مکالمه‌ی خوشایند با شریک‌تون تشبیه کردیم؟ میشه استنتاجِ تایپ رو هم مثل یه شریک خوب که تو حل مسئله کمک‌تون می‌کنه بدونین.

برای مثال می‌تونیم خودمون تابع ‏‎id‎‏ رو بنویسیم:

Prelude> let ourId x = x
Prelude> :t ourId
ourId :: t -> t
Prelude> ourId 1
1
Prelude> ourId "blah"
"blah"

اینجا تایپ تابعِ ‏‎ourId‎‏ رو به استنتاج تایپ ِ هسکل سپردیم. به خاطر تعادلِ آلفا، تفاوتِ حروف (‏‎t‎‏ بجای ‏‎a‎‏ که بالاتر بود) موجب تغییری نمیشه. متغیرهای تایپ، خارج از تایپ سیگنچر ِشون معنایی ندارن.

پیدا کردنِ تایپِ این تابع هم میذاریم به عهده‌ی کامپایلر:

Prelude> let myGreet x = x ++ " Julie"
Prelude> myGreet "hello"
"hello Julie"
Prelude> :type myGreet
myGreet :: [Char] -> [Char]

کامپایلر میدونه که تابع ‏‎(++)‎‏ فقط با یه تایپ کار می‌کنه پس تایپ ‏‎String‎‏ رو نتیجه می‌گیره. تشخیصِ تایپ سیگنچر با چنین اطلاعاتی برای کامپایلر اصلاً زحمتی نداره. ولی اگه نوشته ِ بالا رو با یه متغیر دیگه عوض کنیم:

Prelude> let myGreet x y = x ++ y
Prelude> :type myGreet
myGreet :: [a] -> [a] -> [a]

برگشتیم به یه تایپ سیگنچر ِ پلی‌مورفیک، در واقع تایپ سیگنچر ِ خودِ ‏‎(++)‎‏، چراکه کامپایلر هیچ اطلاعاتی برای استنتاجِ تایپ ِ اون مقادیر نداره (غیر از لیست بودن‌شون).

حالا استنتاج تایپ رو در عمل ببینیم. برنامه‌ی ویرایش متن ِتون رو باز و کُدِ زیر رو وارد کنید:

-- typeInference1.hs
module TypeInference1 where

f :: Num a => a -> a -> a
f x y = x + y + 3

بعد تو GHCi بارگذاری‌ش کنید:

Prelude> :l typeInference1.hs
[1 of 1] Compiling TypeInference1
Ok, modules loaded: TypeInference1.
Prelude> f 1 2
6
Prelude> :t f
f :: Num a => a -> a -> a
Prelude> :t f 1
f 1 :: Num a => a -> a

به خاطر اینکه لفظ‌های عددیِ هسکل تایپ پلی‌مورفیک (محدودیت تایپکلاسی) ِ ‏‎Num a => a‎‏ رو دارن، با اعمالِ ‏‎f‎‏ به ‏‎1‎‏، تایپ معیّن تری نمی‌گیریم.

ببینیم اگه تایپ سیگنچر رو حذف کنیم چه تغییری می‌کنه:

-- typeInference2.hs
module TypeInference2 where

f x y = x + y + 3

‏‎f‎‏ بدونِ تایپ سیگنچر. کامپایل میشه؟ کار می‌کنه؟

Prelude> :l typeInference2.hs
[1 of 1] Compiling TypeInference2
Ok, modules loaded: TypeInference2. 
Prelude> :t f
f :: Num a => a -> a -> a
Prelude> f 1 2
6

هیچ تغییری نکرد. البته تو بعضی موارد تأثیر داره؛ معمولاً وقت‌هایی که از یه تایپکلاس طوری استفاده می‌کنین که مشخص نیست کدوم تایپ منظورتون بوده.

تمرین‌ها: اعمال کنین

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

۱.

-- تایپ سیگنچر تابع کلی
(++) :: [a] -> [a] -> [a]

-- اگه به مقدار زیر اعمال‌ش کنیم
-- چطور تغییر می‌کنه؟
myConcat x = x ++ " yo"

۲.

-- تابع کلی
(*) :: Num a => a -> a -> a

-- اعمال به یه مقدار
myMult x = (x / 3) * 5

۳.

take :: Int -> [a] -> [a]

myTake x = take x "hey you"

۴.

(>) :: Ord a => a -> a -> Bool

myCom x = x > (length [1..10])

۵.

(<) :: Ord a => a -> a -> Bool

myAlph x = x < 'z'