۲۰ - ۷سبک کردنِ کدِ سنگین
یه ذره ما رو تحمل کنین و بدونین که کُد زیر واقعی، ولی عمداً مصنوعی هم هست. یکی از نویسندهها برای خلاصهسازیِ کُد به یکی از دوستاش کمک کرده بود، و کُدِ پایین یه نسخهی ساده شده از کُدیه که نویسنده باهاش شروع کرده بود. یکی از قدرتهای هسکل اینه که میشه فقط برمبنای تایپها، بدونِ مشغله برای کُدی که قراره اجرا بشه کار کرد. این کُد رو از الکس پتروو گرفتیم:
-- مرسی از این مثالِ عالی الِکس
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
توجه رو به نحوهی تغییرِ تایپها و هدفِ برنامه میبره و باعث میشه کُد تمیز بشه.