۳ - ۵تایپهای توابع الحاق
یه نگاه به تایپِ توابعِ (++)
و 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"]