۲ - ۹پرانتزگذاری

اینجا مشخصاتی که GHCi برای چندتا از عملگرهای میانوند با دستور ‏‎:info‎‏ میده رو لیست کردیم. تایپ سیگنچرها رو هم نوشتیم، ولی فعلاً کاری باهاشون نداریم. شاید دیدنشون برای کسانی که کنجکاون جذاب باشه.

Prelude> :info (*)
class Num a where
  (*) :: a -> a -> a
infixl 7 * 

Prelude> :info (+)
class Num a where
  (+) :: a -> a -> a
infixl 6 +

Prelude> :info (-)
class Num a where
  (-) :: a -> a -> a
infixl 6 -

Prelude> :info ($)
($) :: (a -> b) -> a -> b
infixr 0 $

اوپراتورِ ‏‎($)‎‏ رو تو هسکل زیاد می‌بینید، ما هم اینجا یه کم روش وقت میذاریم. خبر خوب اینکه تقریباً هیچ کاری نمی‌کنه. خبر بد اینکه همین موضوع خیلی‌ها رو سردرگم می‌کنه.

اول از همه تعریف‌ش رو ببینیم:

f $ a = f a

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

Prelude> (2^) (2 + 2)
16
-- میتونه جای اون پرانتزها رو بگیره 
Prelude> (2^) $ 2 + 2
16
-- $ بدون پرانتز یا
Prelude> (2^) 2 + 2
6

اوپراتور ‏‎($)‎‏ اجازه میده اول هر چیز سمت راست‌ش هست حساب بشه، و میشه برای تعویقِ اعمالِ توابع ازش استفاده کرد. در فصل ۷، وقتی ترکیب توابع رو توضیح بدیم، منظورمون از تعویقِ توابع روشن‌تر میشه.

توی یه بیانیه از چندتا ‏‎($)‎‏ هم میشه استفاده کرد. برای مثال:

Prelude> (2^) $ (+2) $ 3*2
256

ولی این کار نمی‌کنه:

Prelude> (2^) $ 2 + 2 $ (*30)

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

-- ($) تعریف
f $ a = f a

(2^) $ 2 + 2 $ (*30)

چون ‏‎$‎‏ شرکت‌پذیری از راست (‏‎infixr‎‏) داره، ساده‌سازی رو از راست‌ترین نقطه شروع می‌کنیم.

2 + 2 $ (*30)
-- رو ساده می‌کنیم ($)
(2 + 2) (*30)

برای اینکه بتونیم ‏‎(2 + 2)‎‏ رو اعمال کنیم، اول باید ساده‌ش کنیم:

4 (*30)

خب، جواب شد ‏‎(4 * 30)‎‏، درسته؟ نه! این بیانیه میخواد عدد ۴ رو مثل یه تابع به ‏‎(*30)‎‏ اعمال کنه! و این هیچ مفهومی نداره. به نوشتنِ بیانیه‌ها به این شکل ‏‎(*30)‎‏ میگیم بخش‌بندی.

این مثال رو یه ذره جابجا می‌کنیم تا جواب بده، بعد مراحل ساده شدنش رو می‌بینیم:

(2^) $ (*30) $ 2 + 2
-- اول باید سمت راست رو حساب کنیم
(2^) $ (*30) (2 + 2)
-- (2 + 2) ‎‏به بیانیه‌ی‏‎ (*30) ‎‏اعمال تابع‏‎
-- محاسبه‌ش رو اجبار می‌کنه 
(2^) $ (*30) 4
-- ‎‏رو ساده می‌کنیم‏‎ (*30) 4 ‎‏حالا‏‎
(2^) $ 120
-- ‎‏رو ساده می‌کنیم‏‎ ($) ‎‏دوباره‏‎
(2^) 120
-- ‎‏رو ساده می‌کنیم‏‎ (2^)
1329227995784915872903807060280344576

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

پرانتزگذاری عملگرهای میانوند

گاهی پیش میاد که فقط با خودِ یه اوپراتورِ میانوند کار داشته باشیم (بدون آرگومان)، و گاهی هم به عنوان تابعِ پیشوندی لازم میشن. در هر دو مورد باید عملگر رو بین پرانتز بذاریم. با یه مثال استفاده از عملگرها به عنوان تابعِ پیشوندی رو نشون میدیم.

اگه تابعِ میانوندی‌تون ‏‎>>‎‏ باشه، هر وقت بخواین به عنوان یه مقدار ازش استفاده کنین باید با پرانتز بنویسین، ‏‎(>>)‎‏. با استفاده از پرانتز، ‏‎(+)‎‏ یه تابعِ جمع‌ه که هیچ آرگومانی بهش داده نشده، و ‏‎(1+)‎‏ همون تابع‌ه که به یکی از آرگومان‌هاش اعمال شده. پس ‏‎(1+)‎‏ یه تابع‌ه که ورودی‌ش رو با ۱ جمع می‌کنه و جواب رو خروجی میده:

Prelude> 1 + 2
3
Prelude> (+) 1 2
3
Prelude> (+1) 2
3

موردِ آخر مثالی از بخش‌بندی ِه، و یکی از راه‌های استفاده از توابعِ نیمه اعمال‌شده هست. به دلیل جابجایی‌پذیر بودنِ تابع جمع، تفاوتی بین ‏‎(+1)‎‏ و ‏‎(1+)‎‏ وجود نداره، یعنی ترتیب آرگومان‌ها تأثیری روی جواب نداره.

در مقابل، اگه از بخش‌بندی با توابعی که جابجایی‌پذیر نیستن استفاده کنیم، ترتیب اهمیت پیدا می‌کنه:

Prelude> (1/) 2
0.5
Prelude> (/1) 2
2.0

تفریق یه مورد خاصه. اینها کار می‌کنن:

Prelude> 2 - 1
1
Prelude> (-) 2 1
1

ولی اینطور بخش‌بندی کار نمی‌کنه:

Prelude> (-2) 1

وقتی یه مقدار رو به همراه منها تو پرانتز میذاریم، GHCi اون رو به چشم یک آرگومان برای توابع می‌بینه. و از اونجا که منها بعد از اعمال شدن به آرگومانِ دوم‌ش، به تابعِ ‏‎negate‎‏ تبدیل میشه، GHCi پیغام خطا میده که نمی‌تونه مقدار ‏‎-۲‎‏ رو به عدد ۱ اعمال کنه. منها در این مورد، یه نمونه از سرباری گرامری ِه که باعثِ کمی ابهام میشه.

از بخش‌بندی برای تفریق میشه استفاده کرد، ولی فقط با آرگومانِ اول‌ش:

Prelude> let x = 5
Prelude> let y = (1 -)
Prelude> y x
-4

راه دیگه اینکه به جای ‏‎(- x)‎‏ بنویسین ‏‎(subtract x)‎‏:

Prelude> (subtract 2) 3
1

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