۲۰ - ۶بازبینی مورس کد
برای یه مثال خیلی خوب از کاربردِ 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
بود.