۳ - ۵تایپ‌های توابع الحاق

یه نگاه به تایپِ توابعِ ‏‎(++)‎‏ و ‏‎concat‎‏ بندازیم. تابع ‏‎++‎‏ یک عملگر ِ میانوند ِه. هر وقت لازم بشه یه عملگر ِ میانوندی رو پیشوندی استفاده کنیم (مثل زمانی که بخوایم پشت آرگومان‌ها باشن، یا بخوایم با دستورِ ‏‎:t‎‏ تایپِ‌شون رو پیدا کنیم)، باید دو طرفش پرانتز بذاریم. در مقابل، ‏‎concat‎‏ یه تابعِ معمولی‌ِه (میانوندی نیست)، پس پرانتز لازم نداره:

Prelude> :t (++)
(++) :: [a] -> [a] -> [a]
Prelude> :t concat
concat :: [[a]] -> [a]

تایپِ تابع ‏‎concat‎‏ میگه که یه لیستی از لیست‌ها رو به عنوان ورودی می‌گیره، و یه لیست برمی‌گردونه. و محتوای این لیستِ خروجی، همون تایپی رو دارند که لیستِ لیست در ورودی داره. این تابع، به کلامی، ورودی‌ش رو از دو ساختار (لیست) به یک ساختار لِه می‌کنه. همونطور که گفتیم، ‏‎String‎‏ یه لیست‌ِه؛ لیستی از ‏‎Char‎‏. ورودیِ تابع ‏‎concat‎‏ هم می‌تونه یه لیستی از ‏‎String‎‏ باشه، یا یه لیستی از لیستِ تایپ‌های دیگه:

Prelude> concat [[1, 2], [3, 4, 5], [6, 7]]
[1,2,3,4,5,6,7]
Prelude> concat ["Iowa", "Melman", "Django"]
"IowaMelmanDjango"

(نکته: اگه از ۷٫۱۰ GHC یا جدیدتر استفاده می‌کنین، تایپ سیگنچرِ متفاوتی برای ‏‎concat‎‏ می‌بینید. بعداً به تفصیل توضیح میدیم، ولی فعلاً تایپِ ‏‎Foldable t => t [a]‎‏ رو بخونین ‏‎[[a]]‎‏. الان کفایت می‌کنه که ‏‎Foldable t‎‏ رو یه جور لیستِ دیگه بدونین. در حقیقت، لیست فقط یکی از تایپ‌های ممکن — تایپ‌هایی که نمونه ای از تایپکلاسِ ‏‎Foldable‎‏ دارند — برای این تابع‌ِه، ولی در حال حاضر فقط لیست برای ما مهمه.)

اما معنیِ این تایپ‌ها چیه؟ کمی دقیق‌تر بررسی می‌کنیم:

(++) :: [a] -> [a] -> [a]
--      [1]    [2]    [3]

هر چیزی بعد از ‏‎::‎‏ مرتبط با تایپ‌هاست، و نه مقادیر. حرفِ ‏‎a‎‏ داخلِ نوع‌ساز ِ لیست ،‏‎[]‎‏، یک متغیرِ تایپی ِه.

۱.

یک آرگومان با تایپِ ‏‎[a]‎‏ بگیر. این تایپ، یه لیستی از الِمان‌ها با تایپِ ‏‎a‎‏ ِه که تابع نمی‌دونه اون ‏‎a‎‏ چیه. تایپِ ‏‎a‎‏ بالاخره در جایی از برنامه شناخته و معین میشه.

۲.

یه آرگومانِ دیگه با تایپِ ‏‎[a]‎‏ (یه لیستی از الِمان‌ها که تایپِ‌شون رو نمی‌دونیم) بگیر. چون متغیرهای تایپی یکسان‌اند، پس تایپ‌شون هم باید در کلِ تابع یکسان باشه (‏‎a == a‎‏).

۳.

یک جواب از تایپِ ‏‎[a]‎‏ برگردون.

از اونجا که ‏‎String‎‏ یه نوع لیست‌ِه، عملگرهایی که برای ‏‎String‎‏ استفاده می‌کنیم رو میشه به لیستِ بقیه‌ی تایپ‌ها هم اعمال کرد، مثل لیستِ اعداد. تایپِ ‏‎[a]‎‏ یعنی یه لیستی از الِمان‌ها با تایپِ ‏‎a‎‏ داریم، که هنوز چیزی از اون تایپ نمی‌دونیم. اگه از عملگر ِ الحاقِ لیست‌ها برای لیستِ اعداد استفاده کنیم، اون موقع، ‏‎a‎‏ که در ‏‎[a]‎‏ هست، یکی از تایپ‌های اعداد، مثلاً اعدادِ صحیح، میشه. اگه لیستِ حروف رو الحاق کنیم، ‏‎a‎‏ تایپِ ‏‎Char‎‏ رو نشون میده (و ‏‎String‎‏ هم در واقع ‏‎[Char]‎‏ ِه). متغیرِ تایپی ِ ‏‎a‎‏ در ‏‎[a]‎‏ یک چندریخت ِه. چندریختی یا پلی‌مورفیسم از امکاناتِ مهمِ هسکل‌ه. برای الحاق ِ چند لیست، همه‌ی اونها باید از یک تایپ باشن؛ برای مثال، الحاق یه لیست از اعداد با یه لیست از حروف ممکن نیست. البته متغیرِ ‏‎a‎‏ در سطح تایپ‌هاست، و اصلاً لازم نیست که خودِ مقادیرِ داخل هر کدوم از لیست‌ها یکسان باشن، فقط باید از یک تایپ باشن. به کلامی دیگه، ‏‎a‎‏ باید با ‏‎a‎‏ برابر باشه (‏‎a == a‎‏).

Prelude> "hello" ++ " Chris"
"hello Chris"

ولی:

Prelude> "hello" ++ [1, 2, 3]

<interactive>:14:13:
    No instance for (Num Char) arising
      from the literal ‘1’
    In the expression: 1
    In the second argument of ‘(++)’,
      namely ‘[1, 2, 3]’
    In the expression: "hello" ++ [1, 2, 3]

در مثال اول دو تا نوشته داریم، پس تایپِ ‏‎a‎‏ ها یکسان‌اند — هردوشون ‏‎Char‎‏ اند (از ‏‎[Char]‎‏)، و اهمیتی نداره که مقادیرِشون یکسان نیست. همین که تایپ‌ها با هم جور هستن، هیچ خطای تایپی اتفاق نمی‌افته و نتیجه‌ی الحاق شده رو می‌بینیم.

در مثال دوم، دو تا لیست داریم (یکی ‏‎String‎‏ و اون یکی لیستی از اعداد) که تایپِ‌شون با هم جور نیست، پس با خطا روبرو شدیم. GHCi دنبالِ یک نمونه از تایپکلاسِ اعداد به اسم ‏‎Num‎‏ برای ‏‎Char‎‏ میگرده. تایپکلاس‌ها تعاریفی از عملگرها، یا توابع هستند، که امکانِ به اشتراک گذاشته شدنِ‌شون بین یک دسته از تایپ‌ها وجود داره. اما فعلاً کفایت می‌کنه که این پیغامِ خطا رو به معنیِ جور نبودنِ تایپ‌ها برای الحاق ِ دو لیست بدونین.

تمرین‌ها: خطاهای گرامری

گرامر ِ توابع زیر رو بررسی کنید و بگین آیا کامپایل میشن یا نه. با REPL تست کنید، و هر کجا مشکل گرامری بود، سعی کنید اصلاح شده‌اش رو بنویسین.

۱.

++ [1, 2, 3] [4, 5, 6]

۲.

'<3' ++ ' Haskell'

۳.

concat ["<3", " Haskell"]