使用带镜头的monadic函数修改状态

时间:2017-09-08 08:46:53

标签: haskell monads 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:删除第二次修改的示例。由于导入错误,它没有编译(参见comment)。

1 个答案:

答案 0 :(得分:6)

您在use上使用ASetter,但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,因为modifyObjects中的IOaction中的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 值。如果是这种情况,我们必须确保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 具有相同的类型:

lift

请注意,您需要modifyObjects action :: StateT Game IO () action = modifyingM objects (lift . modifyObjects)

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

我们可以在这里停下来,但为了一些乐趣,让我们再看看Lens:

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 sMonadState,但您希望使用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

        $("li").click(function() {
          $("li").removeClass("selected bold");
          var id = $(this).attr('id');
          $(document).find('[data-parents="'+id+'"]').addClass("bold");
          $(document).find('[data-children="'+id+'"]').addClass("bold");
          $(this).addClass("selected");
        });