首页 > 解决方案 > 如何修复这种嵌套 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功能。

标签: haskellfunctional-programmingnestedfunctormap-function

解决方案


您可以利用函子组合这一事实。虽然你可以通过用 定义新类型来相当明确地表示这一点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]RoleMapColorMap

我们使用函数组合来减少定义中显式嵌套的数量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表达式吗?不需要。但将辅助函数与辅助函数计算的值分开可能会有所帮助。)


推荐阅读