Как происходит связывание функтора - PullRequest
0 голосов
/ 31 октября 2018

Здравствуйте, я читаю Real World Haskell и наткнулся на этот пример из Chapter 10 - Parsing a raw PGM file, где объясняется, как устранить шаблонный код с помощью цепочки функторов:

(>>?) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>? _ = Nothing
Just v  >>? f = f v

-- L.ByteString -> Maybe (Int, L.ByteString)
getNat s = case L8.readInt s of
             Nothing -> Nothing
             Just (num,rest)
                 | num <= 0    -> Nothing
                 | otherwise -> Just (fromIntegral num, rest)

parseP5_take2 :: L.ByteString -> Maybe (Greymap, L.ByteString)
parseP5_take2 s =
    matchHeader (L8.pack "P5") s       >>?
    \s -> skipSpace ((), s)           >>?
    (getNat . snd)                    >>?
    skipSpace                         >>?
    \(width, s) ->   getNat s         >>?
    skipSpace                         >>?
    \(height, s) ->  getNat s         >>?
    \(maxGrey, s) -> getBytes 1 s     >>?
    (getBytes (width * height) . snd) >>?
    \(bitmap, s) -> Just (Greymap width height maxGrey bitmap, s)

skipSpace :: (a, L.ByteString) -> Maybe (a, L.ByteString)
skipSpace (a, s) = Just (a, L8.dropWhile isSpace s)

Я не понимаю следующее: если оператор >>? принимает Maybe a и применяет метод, но возвращает Maybe b, тогда как skipSpace и getNat подходят, так как оба принимают распакованный ( не может быть) argument.
Итак, у вас есть Maybe a, и вы пропустите его через >>?, это означает, что у вас будет Maybe b ... когда этот Maybe b будет распакован, чтобы дать к следующему методу? (в нашем случае getNat или skipSpace?

Я имею в виду, что после каждого >>? и перед каждым методом у вас есть Maybe something, тогда как следующий метод имеет тип nextmethod::something->Maybe somethingElse. Когда Maybe something распаковывается в something для метода, который его использует?


method_0 >>? [Maybe something] method_1 >>? [Maybe somethingElse] method_2

Итак, в [ ] я написал типы, которые получаются из >>? непосредственно перед передачей в методы.
method_1 принимает something, а method_2 принимает somethingElse. Кто делает распаковку для этих 2х методов?

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

Вот другой подход, чтобы объяснить, почему >>? полезен.

Если бы это были обычные функции типа 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
0 голосов
/ 31 октября 2018

(>>?) является оператором infix . При использовании как таковой он принимает Maybe a с левой стороны и функцию (a -> Maybe b) с правой стороны.

getNat соответствует правой стороне, потому что имеет тип L.ByteString -> Maybe (Int, L.ByteString). Здесь a равно L.ByteString, а b равно (Int, L.ByteString).

skipSpace также соответствует правой стороне (>>?). Здесь a равно (a1, L.ByteString), а b равно (a1, L.ByteString). (Я переименовал аргумент типа в функции в a1, чтобы не путать его с a и b из определения типа (>>?).

Так как возвращаемое значение оператора (>>?) равно Maybe b, вы можете продолжать цепочку возвращаемого значения с большим количеством операторов (>>?), что и делает пример; это просто разрывает эту цепочку на несколько строк.

...