۲۰ - ۶بازبینی مورس کد

برای یه مثال خیلی خوب از کاربردِ ‏‎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‎‏ بود.