۲۰ - ۶بازبینی مورس کد
برای یه مثال خیلی خوب از کاربردِ traverse برمیگردیم به کاری که با کدِ مورس در فصلِ تستینگ انجام دادیم. کارمون رو یه دوره کنیم:
stringToMorse :: String -> Maybe [Morse]
stringToMorse s =
sequence $ fmap charToMorse s
-- :کاری که میخوایم انجام بدیم
stringToMorse :: String -> Maybe [Morse]
stringToMorse = traverse charToMorseدر حالت عادی، از نگاشت ِ یه (a -> f b) روی یه t a، انتظارِ یه جوابِ t (f b) میره، اما traverse اون ساختارها رو جابجا میکنه. اگه خاطرتون باشه، هر کاراکتر رو زیرِ Maybe میبردیم چون ممکن بود یه کاراکتر ترجمهی مورس نمیداشت (یا برعکسش، کاراکترِ ورودی مورس نمیبود):
relude> morseToChar "gobbledegook"
othing
relude> morseToChar "-.-."
ust 'c'با fromMaybe میشه لایهی Maybe رو حذف کرد:
relude> import Data.Maybe as M
relude M> fromMaybe ' ' (morseToChar "-.-.")
c'
relude M> stringToMorse "chris"
ust ["-.-.","....",".-.","..","..."]
relude M> fromMaybe [] (stringToMorse) "chris"
"-.-.","....",".-.","..","..."]یه تابعِ کمککننده تعریف میکنیم تا در مثالهای زیر استفاده کنیم:
relude> let morse s = fromMaybe [] (stringToMorse s)
relude> :t morse
orse :: String -> [Morse]حالا اگه morseToChar رو fmap کنیم، یه لیست از مقادیرِ Maybe میگیریم:
relude> fmap morseToChar (morse "chris")
Just 'c', Just 'h',Just 'r',Just 'i',Just 's']تابعِ catMaybe اینجا کاربرد نداره چون نمیخوایم مقادیرِ Nothing نادیده گرفته بشن؛ باید اگه حتی یک مقدارِ Nothing هم توی اون لیست باشه، جواب Nothing بشه. پس تابعی که به دردِمون میخوره، sequence ِه. در تعریفِ اصلیِ stringToMorse از sequence استفاده کردیم، برای جابجا کردنِ تایپها هم کاربرد داره (به جاهای t و m دقت کنین):
relude> :t sequence
equence :: (Monad m, Traversable t) =>
t (m a) -> m (t a)اگه بخوایم ازش روی یه لیست از مقادیرِ Maybe (یا مقادیرِ موندی ِ دیگه) استفاده کنیم، باید با fmap ترکیبِش کنیم:
relude> :t (sequence .) . fmap
sequence .) . fmap
:: (Monad m, Traversable t) =>
(a1 -> m a) -> t a1 -> m (t a)
relude> sequence $ fmap morseToChar (morse "chris")
ust "chris"
relude> sequence $ fmap morseToChar (morse "julie")
ust "julie"اون ترکیب ِ عجیبغریب، که احتمالاً به شکلِ (join .) . fmap هم دیده باشین، به خاطرِ دو آرگومان بودنِ fmap ِه (دو آرگومان میگیره و یک خروجی میده)، پس باید دوبار ترکیب کنیم تا بتونیم fmap رو به دو آرگومان اعمال کنیم.
-- این رو میخوایم
(sequence .) . fmap =
\f xs -> sequence (fmap f xs)
-- نه این
sequence . fmap =
\f -> sequence (fmap f)چنین ترکیبی از sequence و fmap انقدر رایجه که دیگه traverse یکی از تابعهای استاندارِ Prelude شده. کُدِ بالا رو با این پایینی مقایسه کنین:
*Morse T> traverse morseToChar (morse "chris")
Just "chris"
*Morse T> traverse morseToChar (morse "julie")
Just "julie"در نتیجه، از میخ کردنِ fmap به sequence میرسیم به traverse. بخشِ خاصش تابعِ sequence ِه، اما اکثرِ اوقات اول باید fmap کنین، یعنی نهایتاً میرسین به همون traverse. خیلی شبیهِ رسیدن به >>= از ترکیب ِ join با fmap ِه؛ اونجا هم join بخشِ خاصِ Monad بود.