Вот другой подход, чтобы объяснить, почему >>?
полезен.
Если бы это были обычные функции типа a -> b
, мы могли бы просто связать их вместе, используя композицию функций.
f :: a -> b
g :: b -> c
h :: c -> d
h . g . f :: a -> d
Или ввод нового оператора f >>> g = g . f
в качестве «обратной композиции»,
f >>> g >>> h :: a -> d
Однако Maybe
усложняет ситуацию, потому что теперь тип возврата одной функции не совпадает с вводом следующей:
f' :: a -> Maybe b
g' :: b -> Maybe c
h' :: c -> Maybe d
f' >>> g' >>> h' -- type check errors
Однако, поскольку Maybe
является функтором, мы можем использовать fmap
, чтобы применить g'
к возвращаемому значению f'
.
x :: a
f' x :: Maybe b
fmap g' (f' x) :: Maybe (Maybe c)
fmap h' (fmap g' (f' x)) :: Maybe (Maybe (Maybe d))
Но чем больше мы делаем это, тем больше накапливаются обертки; в конечном итоге нам нужно попытаться извлечь значение типа d
из-под всех оболочек.
Некоторые функторы позволяют нам написать функцию, которую я назову join
, которая «уменьшает» слой оберток, «соединяя» их вместе. Maybe
является одним из тех функторов:
join :: Maybe (Maybe a) -> Maybe a
join Nothing = Nothing
join (Just Nothing) = Nothing
join (Just (Just x)) = Just x
Здесь, если обе обертки Just
, мы исключаем одну. Если в стопке появляется Nothing
, мы возвращаем `Nothing. Теперь мы можем написать нашу цепочечную функцию как
fmap g' (f' x) :: Maybe (Maybe c)
join (fmap g' (f' x)) :: Maybe c
fmap h' (join (fmap g' (f' x))) :: Maybe (Maybe d)
join (fmap h' (join (fmap g' (f' x)))) :: Maybe d
Это все еще немного котла, но обратите внимание, что после каждого вызова fmap
,
мы вызываем join
на возвращаемое значение. Мы можем абстрагировать это, используя новый оператор >>?
, который просто отображает свой правый операнд на левый операнд, а затем уменьшает результат.
>>? :: Maybe a -> (a -> Maybe b) -> Maybe b
m >>? f = join (fmap f m)
Используя нового оператора, мы можем упростить длинную цепочку вызовов до fmap
и join
до
f' x >>? g' >>? h'
Должно быть достаточно легко убедить себя в этом Just (f' x) == fmap f' (Just x)
, чтобы мы могли еще больше сгладить нашу цепочку, чтобы она выглядела как
Just x >>? f' >>? g' >>? h'
, который теперь выглядит лот больше как наша оригинальная композиция.
Когда вы прочитаете главу 14 и узнаете о монадах, вы обнаружите, что монады - это просто специальные функторы, такие как Maybe
, для которых вы можете реализовать join
. Кроме того, хотя здесь мы определили >>?
в терминах join
, в Haskell принято определять >>=
(??>
для любой монады, а не только Maybe
) напрямую, а затем определять join
в пересчете на >>=
. С Maybe
это выглядит как
>>? :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>? _ = Nothing
(Just x) >>? f = f x
join :: Maybe (Maybe a) -> Maybe a
join m = m >>? id
-- join Nothing = Nothing >>? id = Nothing
-- join (Just Nothing) = (Just Nothing) >>? id = id Nothing = Nothing
-- join (Just (Just x)) = (Just (Just x)) >>? id = id (Just x) = Just x