我如何修复这种嵌套fmap函数的混乱类型?


data Color = White | Black deriving (Eq, Show)
data Role = King | Queen | Rook deriving (Eq, Show)
data Piece = Piece { color :: Color, 
role  :: Role } deriving (Eq)
data Piese = Piese { piece :: Piece,
coord :: Coord } deriving (Eq)
data ColorMap a = ColorMap {
white :: a,
black :: a
} deriving (Eq)
instance Functor ColorMap where
fmap fn (ColorMap white black) = 
ColorMap (fn white) (fn black)
colorMapFromList :: (a -> Color) -> [a] -> ColorMap [a]
colorMapFromList fn lst = ColorMap 
(filter ((== White) . fn) lst)
(filter ((== Black) . fn) lst)

data RoleMap a = RoleMap {
king  :: a,
queen :: a,
rook  :: a }
instance Functor RoleMap where
fmap fn (RoleMap king queen rook) = 
RoleMap (fn king) (fn queen) (fn rook)
roleMapFromList :: (a -> Role) -> [a] -> RoleMap [a]
roleMapFromList fn lst = RoleMap
(filter ((== King ) . fn) lst)
(filter ((== Queen) . fn) lst)
(filter ((== Rook ) . fn) lst)
mapso :: [Piese] -> ColorMap (RoleMap [Coord])
mapso lst = 
fmap (fmap (fmap coord))                -- ColorMap (RoleMap [Coord])
(fmap (roleMapFromList (role . piece))  -- ColorMap (RoleMap [Piese])
(colorMapFromList (color . piece)   -- ColorMap [Piese]
lst))                            -- [Piese]

我刚刚跳到Haskell,这编译,但它似乎容易出错。有没有一种模式可以化简?特别是mapso函数

您可以利用函子组合的特性。虽然您可以通过使用Data.Functor.Compose定义新类型来相当显式地表示这一点,但实际上,这仅仅意味着您可以将fmap与自身组合在一起。

mapso :: [Piese] -> ColorMap (RoleMap [Coord])
mapso lst = fmap (fmap (fmap coord))
(fmap (roleMapFromList (role.piece)) (colorMapFromList (color.piece) lst))

mapso = (fmap . fmap . fmap) coord . 
fmap (roleMapFromList (role.piece)) .
colorMapFromList (color.piece)

或者做一些重构:

mapso = let fffmap = fmap . fmap . fmap
makeColorMap = colorMapFromList (color.piece)
makeRoleMap = roleMapFromList (role.piece)
in fffmap coord . 
fmap makeRoleMap .
makeColorMap

我已经切换到一个无点的形式来突出三个阶段:

  1. 创建ColorMap
  2. 创建RoleMap
  3. coord映射到[Piese]中包裹的RoleMap中包裹的ColorMap上。

mapso的定义中,我们使用函数组合来减少显式嵌套的数量。

如果你还不习惯考虑函数组合,你可以在let表达式中定义更多的临时变量:

mapso lst = let fffmap = fmap . fmap . fmap
makeColorMap = colorMapFromList (color.piece)
makeRoleMap = fmap (roleMapFromList (role.piece))
in let colorMap = makeColorMap lst
rolemap = makeRoleMap colorMap
in fffmap coord roleMap

我们需要两个let表达式吗?不。但是,将辅助函数与辅助函数计算的值分开可能会有所帮助。

一个选择是使您的地图Monoid实例。所以:

instance Semigroup a => Semigroup (RoleMap a) where
RoleMap ks qs rs <> RoleMap ks' qs' rs' = 
RoleMap (ks <> ks') (qs <> qs') (rs <> rs')
instance Monoid a => Monoid (RoleMap a) where 
mempty = RoleMap mempty mempty mempty
instance Semigroup a => Semigroup (ColorMap a) where
ColorMap ws bs <> ColorMap ws' bs' = 
ColorMap (ws <> ws') (bs <> bs')
instance Monoid a => Monoid (ColorMap a) where 
mempty = ColorMap mempty mempty

现在,提供一个创建单例映射的函数而不是过滤。

singletonRole :: Monoid a => Role -> a -> RoleMap a
singletonRole r a = case r of
King  -> mempty { king  = a }
Queen -> mempty { queen = a }
Rook  -> mempty { rook  = a }
singletonColor :: Monoid a => Color -> a -> ColorMap a
singletonColor c a = case c of
White -> mempty { white = a }
Black -> mempty { black = a }

使用这些,很容易编写一个使用单个Piese的函数:

singletonFromPiese :: Piese -> ColorMap (RoleMap [Coord])
singletonFromPiese (Piese p c) = 
singletonColor (color p) . 
singletonRole (role p) $ [c]

消耗大量的Pieses只是一个foldMap:

mapso :: [Piese] -> ColorMap (RoleMap [Coord])
mapso = foldMap singletonFromPiese

这种方法的一个好处是,对我来说,每个单独的代码片段看起来都很明显,不需要心理类型推断。嵌套的fmap——即使我们认为它们是组合类型上的单个fmap——不具有该属性,至少对我来说是这样。

另一个很好的属性是我们只遍历列表一次;在fmap版本的明显实现中,我们对创建RoleMap的三个过滤器进行了三次传递,对创建ColorMap的两个过滤器进行了两次传递,总共进行了五次传递。

最新更新