۷ - ۳توابع بی‌نام

قبل‌تر، طرز نوشتنِ توابع بی‌نام رو با استفاده از گرامرِ لاندا (که یه خط موربِ برعکس نماینده‌ی کاراکترِ لاندا بود) نشون دادیم. از روی اسم این توابع میشه حدس زد چه کاربردی دارن – ساخت توابعی که بدون اسم استفاده بشن.

این مثال که قبلاً دیدیم، یه تابعِ اسم‌دار، یا غیر-بی‌نام ِه:

triple :: Integer -> Integer
triple x = x * 3

این هم همون تابع با گرامر ِ تابع بی‌نام:

(\x -> x * 3) :: Integer -> Integer

اون پرانتزها لازم‌اند تا اعلام تایپ ِ ‏‎Integer -> Integer‎‏ به کلِ تابع بی‌نام اعمال بشه، و نه فقط به مقدار ۳ با تایپِ ‏‎Num a => a‎‏. تو GHCi اینطوری می‌تونین از بی‌نامی درِش بیارین (یا اسم‌دار ِش کنین):

Prelude> :{
*Main| let trip :: Integer -> Integer
*Main|     trip = \x -> x * 3
*Main| :}
Prelude>

برای اعمال ِ توابع بی‌نام هم عموماً از پرانتز استفاده میشه تا نیّت‌مون واضح بشه:

Prelude> (\x -> x * 3) 5
15
Prelude> \x -> x * 3 1

Could not deduce (Num (a0 -> a))
  arising from the ambiguity check for ‘it’
from the context
  (Num (a1 -> a), Num a1, Num a)
  bound by the inferred type for ‘it’:
    (Num (a1 -> a), Num a1, Num a) => a -> a
  at <interactive>:9:1-13
The type variable ‘a0’ is ambiguous
When checking that ‘it’
  has the inferred type ‘forall a a1.
    (Num (a1 -> a), Num a1, Num a) => a -> a
Probable cause:
  the inferred type is ambiguous

خطای تایپ ِ ‏‎Could not deduce (Num (a1 -> a))‎‏ به خاطر اینه که نمیشه مقادیرِ ‏‎Num a => a‎‏ رو مثلِ تابع‌ها استفاده کنین. به چشمِ کامپیوتر اینطور میاد که می‌خواین از ۳ مثل توابع استفاده کنین و ۳ رو به ۱ اعمال کنین. اینجا ‏‎it‎‏ به ‏‎3 1‎‏ اشاره داره که فکر می‌کنه منظور اعمال ِ ۳ به ۱ بوده (انگار ۳ یه تابع‌ه).*

*

در پیغام‌های خطای ‏‎GHCi‎‏، ‏‎it‎‏ به آخرین بیانیه‌ای که وارد کردین اشاره داره.

تمرین‌ها: زنبیل‌و بردار

مثال‌های زیر در فایل‌های منبع نوشته شدن، نه به طور مستقیم در REPL. البته اگه ترجیح میدین، می‌تونین تغییرشون بدین تا بشه مستقیماً در REPL نوشته بشن.

۱.

کدوم‌ها معادل‌اند (دو یا بیشتر)؟

a)

mTh x y z = x * y * z

b)

mTh x y = \z -> x * y * z

c)

mTh x = \y -> \z -> x * y * z

d)

mTh = \x -> \y -> \z -> x * y * z

۲.

تایپِ ‏‎mTh‎‏ در بالا ‏‎Num a => a -> a -> a -> a‎‏ ِه. تایپِ ‏‎mTh 3‎‏ کدوم یکی از اینهاست؟

a)

Integer -> Integer -> Integer

b)

Num a => a -> a -> a -> a

c)

Num a => a -> a

d)

Num a => a -> a -> a

۳.

حالا گرامر ِ توابع بی‌نام رو تمرین می‌کنیم. برای مثال میشه تابعِ:

addOne x = x + 1

رو اینطور بازنویسی کرد:

addOne = \x -> x + 1 

سعی کنین تعاریف‌تون رو برای GHCi در سطح بالا بنویسین که چک کردن جواب‌هاتون راحت‌تر بشه.

a)

تابعِ ‏‎f‎‏ در عبارتِ ‏‎where‎‏ رو بازنویسی کنین.

addOneIfOdd n = case odd n of
  True -> f n
  False -> n
  where f n = n + 1

b)

تابعِ زیر رو با استفاده از گرامر ِ لاندا ِ بی‌نام بازنویسی کنین.

addFive x y = (if x > y then y else x) + 5

c)

تابع زیر رو بدونِ گرامر ِ لاندا ِ بی‌نام بازنویسی کنین.

mflip f = \x -> \y -> f y x

فایده‌ی گرامرِ لاندا

در کتاب که پیش میریم، این گرامرِ بی‌نام رو زیاد می‌بینین، ولی شاید الان به نظر خیلی مفید نیاد – به نظر برسه فقط یه راهِ دیگه برای تابع نوشتن‌ه.

در بیشترِ مواقع، وقتی یه تابع رو به عنوان آرگومان به یه تابع سطح بالا (که به زودی بیشتر میگیم!) میدیم، و فقط همون یکبار ازش استفاده می‌کنیم، این گرامر کاربرد داره. اگه هیچ وقت قرار نیست تابع رو صدا بزنین، پس اسم هم لازم نداره.

الان خیلی وارد جزئیات‌ش نمیشیم ولی در هسکل جملاتِ اسم‌دار و جملاتِ بی‌نام کمی متفاوت از هم محاسبه میشن، که این هم ممکنه گاهی دلیلی برای استفاده از توابعِ بی‌نام باشه.