۷ - ۹سبک بی‌نقطه

این سبک به ترکیب توابع، بدون مشخص کردن آرگومان‌هاشون اطلاق میشه. برخلاف چیزی که ممکنه به نظر برسه، منظور از "نقطه" عملگر ِ ترکیبِ تابع (‏‎.‎‏) نیست، بلکه نقطه به آرگومان‌ها اشاره داره. به کلامی، ما "نقطه" (عملگر) اضافه می‌کنیم تا بتونیم نقطه‌‌ها (آرگومان‌ها) رو کَم کنیم. اکثر مواقع کُدِ بی‌نقطه خیلی جمع‌وجور و خواناتر میشه، و به خواننده کمک می‌کنه که بیشتر روی توابع تمرکز کنه تا داده‌ها.

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

(f . g) x = f (g x)

هر چقدر تابع‌های بیشتری با هم ترکیب شن، اینطور ترکیب هم خوانایی‌شون رو بیشتر می‌کنه. برای مثال، ‏‎(f. g. h) x‎‏ نسبت به ‏‎(f (g (h x))‎‏ خواناییِ بیشتری داره و تمرکز رو هم بیشتر به تابع منعطف می‌کنه تا آرگومان‌ها. سبک بی‌نقطه در واقع یه بَسطی از همین ایده‌ست، فقط آرگومان‌ها کلاً حذف میشن:

f . g = \x -> f (g x)

f . g . h = \x -> f (g (h x))

با بازنویسی چندتا از مثال‌های بخش قبل به سبکِ بی‌نقطه شروع می‌کنیم تا این سبک رو در عمل ببینیم:

Prelude> let f = negate . sum
Prelude> f [1, 2, 3, 4, 5]
-15

دقت کنین که در تعریف تابعِ ‏‎f‎‏ هیچ پارامتری مشخص نکردیم. ولی با اعمال ِ اون تابع به یه آرگومان، مثل قبل کار می‌کنه.

این تابع رو

f :: Int -> [Int] -> Int
f z xs = foldr (+) z xs

چطور به سبک بی‌نقطه بنویسیم؟

Prelude> let f = foldr (+)
Prelude> f 0 [1..5]
15

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

مثالی دیگه از یه تابعِ کوتاه و بی‌نقطه به همراه نتیجه‌ش. برای این تابع دوباره از ‏‎filter‎‏ استفاده می‌کنیم، اما این بار با عملگر ِ ‏‎==‎‏ که ‏‎Bool‎‏ برمی‌گردونه. با دقت بهش نگاه کنین، و تو ذهن‌تون یا روی کاغذ، پروسه‌ی محاسبه‌ش رو پیش برین:

Prelude> let f = length . filter (== 'a')
Prelude> f "abracadabra"
5

در مثال بعدی، یک مجموعه از توابع رو می‌بینیم که در یه ماژول با هم کار می‌کنن که هم به ترکیب و هم به سبک ِ بی‌نقطه اتکا دارن:

-- arith2.hs
module Arith2 where

add :: Int -> Int -> Int
add x y = x + y

addPF :: Int -> Int -> Int
addPF = (+)

addOne :: Int -> Int
addOne = \x -> x + 1

addOnePF :: Int -> Int
addOnePF = (+1)

main :: IO ()
main = do
  print (0 :: Int)
  print (add 1 0) 
  print (addOne 0) 
  print (addOnePF 0)
  print ((addOne   . addOne) 0)

  print ((addOnePF . addOne) 0)
  print ((addOne   . addOnePF) 0) 
  print ((addOnePF . addOnePF) 0)
  print (negate (addOne 0)) 
  print ((negate . addOne) 0)
  print ((addOne . addOne . addOne
          . negate . addOne) 0)

با حوصله ببینین تک‌تک توابع چه کاری انجام میدن، چه رو کاغذ چه تو ذهن‌تون. بعد هم فایل‌ش رو در GHCi بارگذاری کنین و جواب‌هاتون رو چک کنین.

احتمالاً تا الان درک خوبی از نحوه‌ی ترکیب توابع با ‏‎(.)‎‏ رو پیدا کردین. مهم هست که یادتون بمونه توابع در یک ترکیب، از راست به چپ اعمال میشن، مثل پَک‌مَن* که از راست بِجَوه، و همینطور که میره بیانیه‌ها رو ساده کنه.

*

م. یا به قولِ خودمون، نقطه‌خور!