Haskell 函子链接是如何完成的

Haskell 函子链接是如何完成的,haskell,functor,Haskell,Functor,您好,我正在阅读真实世界的Haskell,我偶然发现了第10章-解析原始PGM文件中的示例,其中解释了如何使用函子链接消除样板代码: (>>?) :: Maybe a -> (a -> Maybe b) -> Maybe b Nothing >>? _ = Nothing Just v >>? f = f v -- L.ByteString -> Maybe (Int, L.ByteString) getNat s = case L

您好,我正在阅读真实世界的Haskell,我偶然发现了第10章-解析原始PGM文件中的示例,其中解释了如何使用函子链接消除样板代码:

(>>?) :: 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)
(>>?)::可能a->(a->可能b)->可能b
什么也没有>>?\u=没有什么
只是v>>?f=f v
--L.ByteString->Maybe(Int,L.ByteString)
getNat s=case L8.readInt s of
无->无
Just(num,rest)
|没什么
|否则->仅(从整数num,rest)
parseP5_take2::L.ByteString->Maybe(Greymap,L.ByteString)
解析p5_take2 s=
火柴头(L8.pack“P5”)s>>?
\s->skipSpace((),s)>>?
(getNat.snd)>>?
skipSpace>>?
\(宽度,s)->getNat s>>?
skipSpace>>?
\(高度,s)->getNat s>>?
\(maxGrey,s)->getBytes 1 s>>?
(getBytes(宽度*高度).snd)>>?
\(位图,s)->仅(灰度贴图宽度高度最大灰度位图,s)
skipSpace::(a,L.ByteString)->可能(a,L.ByteString)
skipSpace(a,s)=仅(a,L8.s)
我不明白以下几点:如果
>?
操作符接受a
可能a
并应用一个方法,但返回a
可能b
,那么
skipSpace
getNat
为什么适合,因为两者都接受一个未绑定的(非绑定的)
参数

那么你有一个
可能是a
并且你把它通过a
>?
,这意味着你将有一个
可能是b
…这个
可能是b
什么时候被取消绑定到下一个方法?(在我们的例子中,
getNat
skipSpace

我的意思是,在每个
>?
之后和每个方法之前,你拥有的是一个
可能是某物
,而下一个方法是类型
nextmethod::something->可能是somethingElse
。对于使用它的方法,
可能是某物
什么时候被解开


method\u 0>>?[可能是某物]method\u 1>>?[可能是某物]method\u 2

因此,在
[]
中,我已经编写了
>?
生成的类型,然后才将其提交给方法。
方法1
接受
某物
,而
方法2
接受
某物
。谁为这两种方法进行拆箱?

(>>)
是一个中缀运算符。当作为中缀运算符使用时,它需要在左侧使用一个
可能是一个
,在右侧使用一个
(a->可能是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
混淆。)
类型定义


由于
(>>?)
运算符的返回值是
可能是b
,因此您可以继续使用更多
(>>?)
运算符链接返回值,这就是示例所做的;它只是在多行上断开该链。

这里有一种不同的方法来解释
>?
为什么有用

如果这些是
a->b
类型的普通函数,我们可以使用函数组合将它们链接在一起

f :: a -> b
g :: b -> c
h :: c -> d

h . g . f :: a -> d
或者引入一个新的操作符作为“反向合成”
f>>g=g.f

但是,
可能会使事情复杂化,因为现在一个函数的返回类型与下一个函数的输入不匹配:

f' :: a -> Maybe b
g' :: b -> Maybe c
h' :: c -> Maybe d

f' >>> g' >>> h'  -- type check errors
但是,由于
可能是一个函子,我们可以使用
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
的值

某些functor允许我们编写一个我将调用的函数
join
,该函数通过将包装层“连接”在一起来“减少”包装层。
可能是这些functor之一:

join :: Maybe (Maybe a) -> Maybe a
join Nothing = Nothing
join (Just Nothing) = Nothing
join (Just (Just x)) = Just x
在这里,如果两个包装器都是
只是
,我们就去掉一个。如果
堆中根本没有出现任何东西,我们就返回'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章并了解单子时,你会发现单子只是一些特殊的函子,比如
也许
,你可以为它们实现
连接
。此外,尽管我们在这里定义了
>?
连接
,Haskell的惯例是定义
>=
??>
对于任何单子,而不仅仅是
可能
)直接定义
连接
,根据
>=
。使用
可能
,如下所示

>>? :: 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

因为,
>?
操作符“取消装箱”该值。它不会调用
上的
f
,可能是一个
值,而是调用
v
上的“取消包装”
>>? :: 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