使用带有镜头的单子函数修改状态

Modify state using a monadic function with lenses

我的问题和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

我正在尝试使用 monadic 函数实现修改 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 提取到通用解决方案 modifingM:

{-# 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 解决方案示例。

编辑 3: 删除第二次编辑的示例。由于导入错误,它没有编译(参见 )。

您在 ASetter 上使用 use,但 use 需要 Getter:

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

不幸的是,ASetterGetting 不一样:

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 

我们需要在ConstIdentity之间任意切换。我们需要 Lens:

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

注意左边没有f。接下来,我们注意到您的 lift 不是必需的。毕竟,f 已经在我们的目标 monad m 中工作了;您之前必须使用 lift,因为 modifyObjectsIO 中,而 actionStateT 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 中设置 new 值。如果是这样,我们必须确保 oldnew 具有相同的类型:

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

请记住,您需要 lift modifyObjects 不过:

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

我们可以就此打住,但为了好玩,让我们再看看 Lens:

type Lens s t a b = forall f. Functor 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

请注意,您需要使用 mtl 中的 Control.Monad.State 而不是 Control.Monad.Trans.State。后者仅定义 put :: Monad m => s -> StateT s m ()get :: Monad m => StateT s m s,但您想使用 mtl.

MonadState 变体

如果我们把所有的东西放在一起,我们看到 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