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
我已经切换到一个无点的形式来突出三个阶段:
- 创建
ColorMap
- 创建
RoleMap
- 将
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]
消耗大量的Piese
s只是一个foldMap
:
mapso :: [Piese] -> ColorMap (RoleMap [Coord])
mapso = foldMap singletonFromPiese
这种方法的一个好处是,对我来说,每个单独的代码片段看起来都很明显,不需要心理类型推断。嵌套的fmap
——即使我们认为它们是组合类型上的单个fmap
——不具有该属性,至少对我来说是这样。
另一个很好的属性是我们只遍历列表一次;在fmap
版本的明显实现中,我们对创建RoleMap
的三个过滤器进行了三次传递,对创建ColorMap
的两个过滤器进行了两次传递,总共进行了五次传递。