۴ - ۷توپلها
چندتایی تایپیه که امکانِ استفاده از چند مقدار داخلِ یک مقدار رو ممکن میکنه. توپلها گرامر ِخاصی دارن که هم در سطح جملهای و هم در سطح نوعی نوشته میشه، و هر توپِل هم تعداد اجزاء ثابتی داره. توپلها رو براساس تعداد مقادیر داخلشون شناسایی میکنیم: برای مثال، توپلِ دوتایی یا جفت، دو مقدار داخلش هست، (x,y)
؛ توپلِ سهتایی یا سهگانه، سه مقدار، (x,y,z)
؛ و همینطور بقیهی توپلها. به این تعدادِ مقادیر توپلها، آریتی ِتوپِل هم میگن. جلوتر میبینیم که لازم نیست مقادیر داخل چندتاییها از یک تایپ باشند.
با یه جفت شروع میکنیم، توپِلی که دو تا الِمان داره. توپلِ دوتایی در هر دو سطحِ جملهای و سطح نوعی با سازندهو ِ(,)
نشون داده میشه. تایپش اینطوری تعریف شده:
Prelude> :info (,)
data (,) a b = (,) a b
علاوه بر گرامر ِخاصش، فرقهای مهمی بین این تعریف با تعریفی مثل Bool
وجود داره. اول اینکه دو تا پارامتر داره، که با متغیرهای تایپیِ a
و b
معلوم شدن. همونطور که در سطحِ جملهای ِکد، پارامترهای توابع بعد از اعمال شدن به آرگومانها با مقادیرِ معین جایگزین میشن، این پارامترهای تایپی هم باید به تایپهای معین اعمال بشن. تفاوتِ اساسی دوم اینه که تعریف بالا یه تایپِ ضرب ِه، نه یه تایپِ جمع مثل Bool
.وتایپِ ضرب معادلِ عطف ِمنطقیه، یعنی برای ساختِ یه مقدار، باید هر دو آرگومان رو براش تأمین کنید.
دقت کنید که در بالا، دو تا متغیرِ تایپ با هم فرق دارن؛ پس یه توپلِ دوتایی، میتونه دو مقدار با تایپهای متفاوت رو نگه داره. با این حال، این تفاوت الزامی نیست:
λ> (,) 8 10
(8,10)
λ> (,) 8 "Julie"
(8,"Julie")
λ> (,) True 'c'
(True,'c')
اگه فقط به یه آرگومان اعمالش کنیم:
λ> (,) 9
<interactive>:34:1:
No instance for (Show (b0 -> (a0, b0)))
(maybe you haven't applied enough
arguments to a function?)
arising from a use of ‘print’,
In the first argument of ‘print’,
namely ‘it’
In a stmt of an interactive
GHCi command: print it
عجب خطایی... به زودی با جزئیات بررسیش میکنیم. اما فعلاً پرانتزِ دوم برامون مهمه:
maybe you haven't applied enough arguments to a function?
-- شاید آرگومانهای کافی به یه تابع ندادین؟
تابعمون – در این مورد دادهسازو ِ(,)
– رو به آرگومانهای کافی اعمال نکردیم.
در هسکل، بطور پیشفرض چند تابعِ استاندارد برای گرفتن مقدار اول و مقدار دوم ِتوپلهای دوتایی تعریف شدن، fst
و snd
:
fst :: (a, b) -> a
snd :: (a, b) -> b
از تایپ سیگنچرِشون مشخصه که اون دو تابع هیچ کاری غیر از برگردوندنِ اولین و دومین مقدار نمیکنن.
اینجا چند مثال از کارهایی که با توپلها میشه انجام داد آوردیم:
Prelude> let myTup = (1 :: Integer, "blah")
Prelude> :t myTup
myTup :: (Integer, [Char])
Prelude> fst myTup
1
Prelude> snd myTup
"blah"
Prelude> import Data.Tuple
Prelude> swap myTup
("blah",1)
تابع swap
در Prelude
تعریف نشده، برای همین Data.Tuple
رو وارد کردیم.
توپلها رو با بیانیههای دیگه هم میشه ترکیب کرد:
Prelude> 2 + fst (1, 2)
3
Prelude> 2 + snd (1, 2)
4
گرامر ِتوپلها، (x,y)
،وگرامر ِخاصیه. سازندههایی که برای توپلها استفاده میکنین، چه در تایپ سیگنچرها و چه در خودِ کد (سطح جملهای)، با اینکه دو کاربرد متفاوت دارن، ولی گرامر ِشون یکسانه. این نوعساز رو گاهی اوقات به تنهایی میبینید، یعنی (,)
بدون متغیرهای تایپیش؛ بقیهی مواقع هم (بخصوص در تایپ سیگنچرها) اینطوری (a,b)
.
در نوشتنِ توابع هم میتونین از اون گرامر برای تطبیق الگو استفاده کنین. یکی از خوبیهاش اینه که بعضی اوقات تعریف تابع خیلی شبیه تایپ سیگنچرِش میشه. برای مثال، توابع fst
و snd
رو خودمون مینویسیم:
fst' :: (a, b) -> a
fst' (a, b) = a
snd' :: (a, b) -> b
snd' (a, b) = b
یه مثال دیگه برای تطبیق الگو روی توپلها:
tupFunc :: (Int, [a])
-> (Int, [a])
-> (Int, [a])
tupFunc (a, b) (c, d) =
((a + c), (b ++ d))
استفاده از توپلهای زیادی بزرگ کار عاقلانهای نیست، بازدهی ِمناسبی هم نداره. بیشتر توپلهایی که میبینین (,,,,)
(توپلِ پنجتایی) یا کوچکتراند.