۲۰ - ۷سبک کردنِ کدِ سنگین

یه ذره ما رو تحمل کنین و بدونین که کُد زیر واقعی، ولی عمداً مصنوعی هم هست. یکی از نویسنده‌ها برای خلاصه‌سازیِ کُد به یکی از دوستاش کمک کرده بود، و کُدِ پایین یه نسخه‌ی ساده شده از کُدی‌ه که نویسنده باهاش شروع کرده بود. یکی از قدرت‌های هسکل اینه که میشه فقط برمبنای تایپ‌ها، بدونِ مشغله برای کُدی که قراره اجرا بشه کار کرد. این کُد رو از الکس پتروو گرفتیم:

-- مرسی از این مثالِ عالی الِکس
data Query     = Query
data SomeObj   = SomeObj
data IoOnlyObj = IoOnlyObj
data Err       = Err

-- .یه تابعی که نوشته رو تبدیل به شئ می‌کنه
-- .گفته میشه decoder به اینجور توابع
decodeFn :: String -> Either Err SomeObj
decodeFn = undefined

-- این هم یه تابع برای استعلام
-- از پایگاه داده‌ست و یه آرایه
-- .برمی‌گردونه string از
fetchFn :: Query -> IO [String]
fetchFn = undefined

-- "‎‏ساختارساز‏‎" یه جور
-- .هم داره IO که
makeIoOnlyObj :: [SomeObj]
              -> IO [(SomeObj, IoOnlyObj)]
makeIoOnlyObj = undefined

-- قبل از خلاصه‌سازی
pipelineFn
  :: Query
  -> IO (Either Err [(SomeObj, IoOnlyObj)])
pipelineFn query = do
  a <- fetchFn query
  case sequence (map decodeFn a) of
    (Left  err) -> return $ Left $ err
    (Right res) -> do
      a <- makeIoOnlyObj res
      return $ Right a

هدف، تمیزکاریِ این کُد بود. چندتا چیز آدم رو به طولانی شدنِ کُد مشکوک می‌کرد:

۱.

استفاده از ‏‎sequence‎‏ و ‏‎map‎‏.

۲.

تطبیق ِ case ِ دستی روی جوابِ ‏‎sequence‎‏ و ‏‎map‎‏.

۳.

بایند ِ موندی روی ‏‎Either‎‏ فقط برای پیاده‌سازی ِ یه اجراییه‌ی موندی (‏‎IO‎‏) ِ دیگه داخل‌ش.

ما تابع pipeline رو اینطوری خلاصه کردیم:

pipelineFn
  :: Query
  -> IO (Either Err [(SomeObj, IoOnlyObj)])
pipelineFn query = do
  a <- fetchFn query
  traverse makeIoOnlyObj (mapM decondeFn a)

مرسی از merijn از کانال IRC که تو این مسئله کمک کرد. اگه بخوایم میشه بی‌نقطه هم بنویسیم:

pipelineFn
  :: Query
  -> IO (Either Err [(SomeObj, IoOnlyObj)])
pipelineFn =
  (traverse makeIoOnlyObj
  . mapM decondeFn =<<) . fetchFn

‏‎mapM‎‏ هم که همون ‏‎traverse‎‏ ِه، فقط تایپ‌ش یه کم فرق داره. پس:

pipelineFn
  :: Query
  -> IO (Either Err [(SomeObj, IoOnlyObj)])
pipelineFn =
  (traverse makeIoOnlyObj
  . traverse decondeFn =<<) . fetchFn

اینطور بی‌شاخ‌وبرگ و خلاصه، سبکی‌ه که هسکل‌نویس‌ها ترجیح میدن. همونطور هم که قبلاً گفته بودیم، بی‌نقطه نوشتن کمک می‌کنه توجه بیشتر متمرکزِ خودِ تابع بشه تا داده‌هایی که به عنوانِ آرگومان داده میشن. استفاده از تابع‌هایی مثل ‏‎traverse‎‏ توجه رو به نحوه‌ی تغییرِ تایپ‌ها و هدفِ برنامه می‌بره و باعث میشه کُد تمیز بشه.