«جنریک» یک ویژگی زبانی است که امکان استفادهی دوباره از کد را فراهم میکند.
برای مثال، نباید مهم باشد که یک لیست چه نوعی را در خود نگه میدارد تا بتوان طول آن را محاسبه کرد.
به همین دلیل است که تابع length در امضای نوع خود از یک پارامتر نوع جنریک به اسم a استفاده میکند.
length : List a -> Int
وقتی یک نوع نسبت به نوع دیگری جنریک است، برای آن نوع دیگر از یک اسم با حروف کوچک استفاده میکنیم و آن را «پارامتر نوع» مینامیم.
مثالهای رایج، نوعهای سفارشی List، Maybe و Result هستند.
-- Maybe is generic over the type a
type Maybe a
= Nothing
| Just a
someString : Maybe String
someString = Just "hello"
someInt : Maybe Int
someInt = Just 42
یک نکتهی مهم دربارهی اسم پارامترهای نوع این است که میتوانند بیش از یک کاراکتر داشته باشند. تنها محدودیت این است که باید اسمهایی با حروف کوچک باشند. به همین دلیل، هر جا مفید باشد، تمایل داریم اسمهایی توصیفی برایشان انتخاب کنیم.
یک نوع میتواند نسبت به چند نوع دیگر جنریک باشد و در نتیجه چندین پارامتر نوع داشته باشد. در آن صورت، هر نوع مستقل یک اسم متفاوت با حروف کوچک میگیرد. مستقل بودن مانع از آن نمیشود که آنها نوع یکسانی شوند، فقط به آنها امکان میدهد متفاوت باشند.
-- Result is generic over the types error and value
type Result error value
= Ok value
| Err error
نامهای مستعار نوع رکورد هم میتوانند پارامتر نوع داشته باشند، هرچند این کار کمتر رایج است.
-- Event is generic over the type a
type alias Event a =
{ at : Time
, data : a
}
از آنجا که نوعها میتوانند جنریک باشند، توابع هم میتوانند جنریک باشند.
میتوان توابعی نوشت که با نوعهای جنریک کار میکنند، مانند Maybe.withDefault.
withDefault : a -> Maybe a -> a
withDefault default maybe =
case maybe of
Just value -> value
Nothing -> default
توابعی که توابع دیگر را به عنوان پارامتر میگیرند، توابع مرتبهبالا نامیده میشوند و اینها هم میتوانند جنریک باشند.
map : (a -> b) -> Maybe a -> Maybe b
map function maybe =
case maybe of
Just value ->
Just (function value)
Nothing ->
Nothing
در این تمرین میخواهید یک TreasureChest «جنریک» (یا بهتر است بگوییم، جادویی!) بنویسید تا مقداری گنج را در آن نگهداری کنید.
TreasureChest را تعریف کنیدیک نوع دادهی سفارشی به نام TreasureChest تعریف کنید که تنها یک «گونه» دارد.
این گونه باید یک «مقدار همراه» از نوع String داشته باشد که برای نگهداری رمز عبور صندوق گنج به کار میرود.
همچنین این گونه باید یک مقدار همراه جنریک داشته باشد که برای نگهداری گنج به کار میرود.
این مقدار جنریک است تا گنج بتواند هرچه باشد.
getTreasure را تعریف کنیداین تابع باید دو پارامتر بگیرد:
String (برای امتحان کردن رمز عبور)TreasureChest
این تابع باید رمز عبور واردشده را با String موجود در TreasureChest بررسی کند.
این تابع باید یک Maybe برگرداند.
اگر رمزهای عبور مطابقت داشتند، Just را همراه با مقدار جنریک درون TreasureChest برگردانید (همان گنج!).
اگر رمزهای عبور مطابقت نداشتند، Nothing را برگردانید.
multiplyTreasure را تعریف کنیداین تابع (مرتبهبالا) باید دو پارامتر بگیرد:
TreasureChest که آن هم همان نوع جنریک را دارداین تابع باید تابع ضربکننده را با گنج موجود در صندوق فراخوانی کند.
این تابع باید یک TreasureChest تازه برگرداند که همان رمز عبور اصلی را دارد و گنج ضربشده (همان مقدار برگشتی) در آن قرار گرفته است.