۷ - ۱۲تعاریف

۱.

انقیاد یا مقیّد (binding یا bound) لغتی‌ه که برای توصیف ارتباط یا اتصالِ بین دو شیء به کار میره. در هسکل برای مقداری که یه متغیر داره استفاده می‌کنیم، مثلاً جمله‌ی "متغیرِ پارامتر به یک آرگومان مقیّد شده" به معنای اینه که اون مقدار به عنوان ورودی به پارامتر داده شده، و هر جایی که اون پارامتر نوشته شده، همون مقدار رو داره. این لغت در فرمِ جمع (انقیادها یا bindings) معمولاً به مجموعه‌ای از متغیرها و توابعی که نام‌گذاری شدن اشاره داره.

blah :: Int
blah = 10

اینجا متغیرِ ‏‎blah‎‏ به مقدارِ ‏‎10‎‏ مقیّد شده.

۲.

تابع بی‌نام یا anonymous function تابعی ِه که به هیچ معرّف ای مقیّد نشده و برای ساخت یه تابع دیگه و یا به عنوان آرگومان به یه تابع دیگه استفاده میشه. مثال‌های زیر رو ببینید.

\x -> x
-- id نسخه‌ی بی‌نام

id x = x
-- 'id' بی‌نام نیست، مقید شده به

۳.

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

-- uncurry و curry توابع
-- تعریف شدن Prelude در

curry' :: ((a, b) -> c) -> a -> b -> c
curry' f a b = f (a, b)

uncurry' :: (a -> b -> c) -> ((a, b) -> c)
uncurry' f (a, b) = f a b

-- نشده، یک توپل curry تابع
-- از آرگومان‌هاش می‌گیره
add :: (Int, Int) -> Int
add (x, y) = x + y

add' :: Int -> Int -> Int
add' = curry' add

تابعی که در ظاهر دو آرگومان می‌گیره، در واقع دو تابع‌ه که هر کدوم یک آرگومان می‌گیرن و یک خروجی میدن. دلیل اینکه چنین رَوَندی کار می‌کنه، اینه که توابع می‌تونن یه تابع دیگه خروجی بدن.

f a b = a + b

-- معادل است با

f = \a -> (\b -> a + b)

۴.

تطبیق الگو یا pattern matching راهی‌ه برای تخریبِ تایپ‌های جمع و ضرب، و دسترسی به محتویات‌شون. برای تایپ‌های ضرب، تطبیق الگو امکانِ افشا کردنِ محتویاتِ اونها و انقیاد ِ مقادیرشون به اسم‌ها رو فراهم می‌کنه. در مورد تایپ‌های جمع هم، با تطبیق الگو، بسته به اینکه کدوم یکی از مقادیرِ تایپ جمع منطبق شده، میشه رفتار متمایزی تعریف کرد. بهترین راه برای توضیحِ تطبیق الگو، بیانِ طرز کارِ نوع‌داده‌‌هاست، پس ما هم اینجا از نوشتاری استفاده می‌کنیم که ممکنه الان کامل متوجه نشین. به زودی این رو عمیقاً بررسی می‌کنیم.

-- .nullary داده‌ساز پوچ‌گانه یا
-- (sum type) نه تایپ جمع
-- ،(product type) و نه تایپ ضرب
-- .فقط یک مقدار مجرد
data Blah = Blah

تطبیق الگو روی ‏‎Blah‎‏ فقط یک کار می‌تونه انجام بده.

blahFunc :: Blah -> Bool
blahFunc Blah = True


data Identity a =
  Identity a
  deriving (Eq, Show)

‏‎Identity‎‏ یه داده‌ساز ِ یگانه ِه. هنوز تایپِ ضرب نیست، فقط حاویِ یک مقداره.

-- تطبیق الگو انجام Identity وقتی روی
-- .رو افشا کنین a میدین، می‌تونین

unpackIdentity :: Identity a -> a
unpackIdentity (Identity x) = x

-- اما می‌تونین محتویات
-- رو نادیده بگیرین Identity

ignoreIdentity :: Identity a -> Bool
ignoreIdentity (Identity _) = True

-- یا کلاً نادیده بگیرین‌ش، چراکه
-- انطباق روی یه داده‌ساز غیر-جمع‌
-- .چیزی رو تغییر نمیده

ignoreIdentity' :: Identity a -> Bool
ignoreIdentity' _ = True
data Product a b =
  Product a b
  deriving (Eq, Show)

با نوع‌داده ِ ‏‎Product‎‏ می‌تونیم از یکی، هر دو، یا هیچ کدوم از مقادیر در ضرب ِ ‏‎a‎‏ و ‏‎b‎‏، استفاده کنیم:

productUnpackOnlyA :: Product a b -> a
productUnpackOnlyA (Product x _) = x

productUnpackOnlyB :: Product a b -> b
productUnpackOnlyB (Product _ y) = y

یا میشه هر دوشون رو به اسم‌های مختلف انقیاد داد:

productUnpack :: Product a b -> (a, b)
productUnpack (Product x y) = (x, y)

اگه همه‌ی مقادیرِ یه تایپِ ضرب رو به یک اسم انقیاد بدین چه اتفاقی میوفته؟

data SumOfThree a b c =
    FirstPossible a
  | SecondPossible b
  | ThirdPossible c
  deriving (Eq, Show)

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

sumToInt :: SumOfThree a b c -> Integer
sumToInt (FirstPossible _)  = 0
sumToInt (SecondPossible _) = 1
sumToInt (ThirdPossible _)  = 2

-- هر کدوم رو هم که بخوایم
-- می‌تونیم نادیده بگیریم

sumToInt :: SumOfThree a b c -> Integer
sumToInt (FirstPossible _)  = 0
sumToInt _                  = 1

-- هنوز باید همه‌ی حالت‌ها
-- رو در نظر بگیریم

تطبیق الگو مرتبط با داده‌هاست.

۵.

تهی یا bottom یه غیرمقدار ِه که نشون میده برنامه توانِ برگردوندن یه مقدار یا جواب رو نداره. بیشتر زمانی این اتفاق میوفته که برنامه در حلقه‌ی بینهایت گیر بیوفته. حالت دیگه‌ای که ممکنه پیش بیاد، اینه که تابعی همه ورودی‌هاش رو در نظر نگیره و در یه تطبیق الگو شکست بخوره. در زیر چند مثال از تهی آوردیم:

-- این رو به هر مقداری اعمال
-- کنین، تا اَبد به خودش
-- (می‌کنه recurse) برمی‌گرده
f x = f x

-- منفجر میشه False با ورودیِ 
dontDoThis :: Bool -> Int
dontDoThis True = 1
-- در ذات، معادل با
definitelyDontDoThis :: Bool -> Int
definitelyDontDoThis True = 1
definitelyDontDoThis False = error "oops"

-- .استفاده نکنین error از
-- .به زودی راه بهتری معرفی می‌کنیم

تهی برای بررسی مسیر محاسبه‌ی کُد به کار میاد (مثل قناری تو معدن!). معمولاً از این روش برای سنجشِ میزانِ تنبلی ِ برنامه‌مون استفاده می‌کنیم. تو فصلی که راجع به نااَکید بودن صحبت می‌کنیم، خیلی ازش می‌بینیم.

۶.

توابع سطح بالا یا higher-order functions توابعی هستند که یا به عنوان جواب، تابع برمی‌گردونن، و یا به عنوان آرگومان تابع می‌گیرین. به خاطرِ کاری کردن، در هسکل هر تابعی که به ظاهر بیشتر از یک آرگومان می‌گیره، تابعِ سطح بالا محسوب میشه.

-- currying به خاطر
-- در واقع سطح بالاست
Int -> Int -> Int

-- مثال‌های زیر همگی تایپ‌های
-- توابع سطح بالا هستن

(a -> b) -> a -> b

(a -> b) -> [a] -> [b]

(Int -> Bool) -> [Int] -> [Bool]

-- ،این هم سطح بالاست
-- ،آرگومان اولی که این تابع می‌گیره
-- .خودش یه تابع سطح بالای دیگه‌ست

((a -> b) -> c) -> [a] -> [c]

۷.

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

(.) :: (b -> c) -> (a -> b) -> a -> c

-- برابر است با

(.) :: (b -> c) -> (a -> b) -> (a -> c)

-- یا

(.) :: (b -> c) -> ((a -> b) -> (a -> c))

-- میشه اینطور تعریف‌ش کرد
comp :: (b -> c) -> ((a -> b) -> (a -> c))
comp f g x = f (g x)

تابعِ ‏‎g‎‏ به ‏‎x‎‏، و ‏‎f‎‏ به جوابِ ‏‎g x‎‏ اعمال شده.

۸.

بی‌نقطه یا pointfree یه جور برنامه‌نویسیِ ضمنی، یا کدنویسی بدون اشاره به آرگومان‌ها با اسم‌شون هست. معمولاً این سبک نوشتار، به لطف کاری کردن، کد رو خیلی جمع‌وجورتر می‌کنه، چون ممکنه داده‌‌ها به طور ضمنی ردوبدل بشن، و یا آرگومان‌های اضافه حذف بشن. "نقطه" در عبارتِ بی‌نقطه به آرگومان اشاره داره.

-- بی‌نقطه نیستن
blah x = x
addAndDrop x y = x + 1
reverseMkTuple a b = (b, a)
reverseTyple (a, b) = (b, a)

-- نسخه‌ی بی‌نقطه‌ی توابع بالا
blah = id
addAndDrop = const . (1 +)
reverseMkTuple = flip (,)
reverseTyple = uncurry (flip (,))

برای مثال‌های بیشتر، برین به صفحه‌ی بی‌نقطه در سایتِ Haskell Wiki.