代码之家  ›  专栏  ›  技术社区  ›  maiermic

使用带透镜的一元函数修改状态

  •  3
  • maiermic  · 技术社区  · 7 年前

    How to modify using a monadic function with lenses? 作者问是否存在类似的情况

    overM :: (Monad m) => Lens s t a b -> (a -> m b) -> s -> m t
    

    答案是 mapMOf

    mapMOf :: Profunctor p =>
         Over p (WrappedMonad m) s t a b -> p a (m b) -> s -> m t
    

    MonadState

    modifyingM :: MonadState s m => ASetter s s a b -> (a -> m b) -> m ()
    

    没有的示例 modifingM :

    {-# LANGUAGE TemplateHaskell #-}
    
    module Main where
    
    import Control.Lens (makeLenses, use, (.=))
    import Control.Monad.Trans.Class (lift)
    import Control.Monad.Trans.State.Lazy (StateT(StateT), execStateT)
    
    data GameObject = GameObject
      { _num :: Int
      } deriving (Show)
    
    data Game = Game
      { _objects :: [GameObject]
      } deriving (Show)
    
    makeLenses ''Game
    
    makeLenses ''GameObject
    
    defaultGame = Game {_objects = map GameObject [0 .. 3]}
    
    action :: StateT Game IO ()
    action = do
      old <- use objects
      new <- lift $ modifyObjects old
      objects .= new
    
    modifyObjects :: [GameObject] -> IO [GameObject]
    modifyObjects objs = return objs -- do modifications
    
    main :: IO ()
    main = do
      execStateT action defaultGame
      return ()
    

    action 到通用解决方案 莫迪芬格姆

    {-# LANGUAGE TemplateHaskell #-}
    
    module Main where
    
    import Control.Lens (makeLenses, use, (.=), ASetter)
    import Control.Monad.State.Class (MonadState)
    import Control.Monad.Trans.Class (lift)
    import Control.Monad.Trans.State.Lazy (StateT(StateT), execStateT)
    
    data GameObject = GameObject
      { _num :: Int
      } deriving (Show)
    
    data Game = Game
      { _objects :: [GameObject]
      } deriving (Show)
    
    makeLenses ''Game
    
    makeLenses ''GameObject
    
    defaultGame = Game {_objects = map GameObject [0 .. 3]}
    
    modifyingM :: MonadState s m => ASetter s s a b -> (a -> m b) -> m ()
    modifyingM l f = do
      old <- use l
      new <- lift $ f old
      l .= new
    
    action :: StateT Game IO ()
    action = modifyingM objects modifyObjects
    
    modifyObjects :: [GameObject] -> IO [GameObject]
    modifyObjects objs = return objs -- do modifications
    
    main :: IO ()
    main = do
      execStateT action defaultGame
      return ()
    

    这会导致编译时错误:

    Main.hs:26:14: error:
        • Couldn't match type ‘Data.Functor.Identity.Identity s’
                         with ‘Data.Functor.Const.Const a s’
          Expected type: Control.Lens.Getter.Getting a s a
            Actual type: ASetter s s a b
        • In the first argument of ‘use’, namely ‘l’
          In a stmt of a 'do' block: old <- use l
          In the expression:
            do { old <- use l;
                 new <- lift $ f old;
                 l .= new }
        • Relevant bindings include
            f :: a -> m b (bound at app/Main.hs:25:14)
            l :: ASetter s s a b (bound at app/Main.hs:25:12)
            modifyingM :: ASetter s s a b -> (a -> m b) -> m ()
              (bound at app/Main.hs:25:1)
    
    Main.hs:31:10: error:
        • Couldn't match type ‘IO’ with ‘StateT Game IO’
          Expected type: StateT Game IO ()
            Actual type: IO ()
        • In the expression: modifyingM objects modifyObjects
          In an equation for ‘action’:
              action = modifyingM objects modifyObjects
    

    有什么问题?


    编辑1: new old 价值

    编辑2: 添加了不编译的@Zeta解决方案示例。

    删除第二次编辑的示例。由于导入错误,它无法编译(请参阅 comment

    1 回复  |  直到 7 年前
        1
  •  8
  •   Zeta    7 年前

    您正在使用 use ASetter 但是 Getter :

    use  :: MonadState s m => Getting a s a        -> m a 
    (.=) :: MonadState s m => ASetter s s a b -> b -> m ()
    

    不幸地 Getting

    type Getting r s a   = (a -> Const r a ) -> s -> Const r s 
    type ASetter s t a b = (a -> Identity b) -> s -> Identity t 
    

    Const Identity 任意地。我们需要一个 Lens

    type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
    

    请注意,没有 f 在左侧。接下来,我们注意到您的 lift 没有必要。毕竟 f 已经在我们的目标单子中工作 m 举起 modifyObjects 是在 IO action 是在 StateT Game IO m :

    modifyingM :: MonadState s m => Lens s s a a -> (a -> m b) -> m ()
    modifyingM l f = do
      old <- use l
      new <- f old
      l .= old
    

    真管用!但这可能是错误的,因为您可能想要设置 中的值 l .= old . 如果是这样,我们必须确保 old new 具有相同类型:

    --                                      only a here, no b
    --                                       v v     v      v
    modifyingM :: MonadState s m => Lens s s a a -> (a -> m a) -> m ()
    modifyingM l f = do
      old <- use l
      new <- f old
      l .= new
    

    举起 尽管如此:

    action :: StateT Game IO ()
    action = modifyingM objects (lift . modifyObjects)
    

    类型Lens s t a b=forall f。函子f=>(a->f b)->s->f.t
    

    对于任何 a -> f b s -> f t . 因此,如果我们只是在你的对象中插入一些东西,我们有

    > :t \f -> objects f
    \f -> objects f
      :: Functor f => (GameObject -> f GameObject) -> Game -> f Game
    

    因此,我们只需要一些 MonadState s m => (s -> m s) -> m ()

    import Control.Monad.State.Lazy (get, put) -- not the Trans variant!
    
    modifyM :: MonadState s m => (s -> m s) -> m ()
    modifyM f = get >>= f >>= put
    

    注意,您需要使用 Control.Monad.State 从…起 mtl 而不是 Control.Monad.Trans.State put :: Monad m => s -> StateT s m () get :: Monad m => StateT s m s MonadState mtl公司

    如果我们把所有的东西放在一起,我们就会看到 modifyingM

    modifyingM :: MonadState s m => Lens s s a a -> (a -> m a) -> m ()
    modifyingM l f = modifyM (l f)
    

    l f :

    modifyingM :: MonadState s m => Lens s s a a -> (a -> m a) -> m ()
    modifyingM l f = use l >>= f >>= assign l