首页 文章

State Monad具有多个州的 Value 观

提问于
浏览
11

考虑以下:

do
  x1 <- new 2
  set x1 3
  x2 <- get x1
  y1 <- new 10
  set y1 20
  y2 <- get y1
  return (x2 + y2)

我想要这导致 23 . 有没有办法在纯Haskell中实现这样的东西,如果是这样的话怎么样?我理解 STRef 做了类似的事情,但我只是想在普通的Haskell中做到这一点(现在不担心效率) . 我假设我必须创建一个数据类型并使其成为 Monad 的实例,但我不确定细节,所以一个有用的例子会有所帮助 .

4 回答

  • 2

    Control.Monad.State 中已经有了一个实现,但是为了一般性起见它很麻烦:一个复杂性来自MonadState类,另一个来自于普通 State 是用更通用的 StateT 来实现的 .

    以下是使用该实现的任务示例 . 没有使用可变性 . 请注意,您的示例按原样粘贴,只需添加 x 前缀:

    import Control.Monad.State
    import qualified Data.Map as M
    
    type MyMap a = M.Map Int a
    type MyState a b = State (MyMap a) b
    type MyRef = Int
    
    xrun :: MyState a b -> b
    xrun x = evalState x (M.empty)
    
    mget :: MyState a (MyMap a)
    mget = get
    
    mput :: MyMap a -> MyState a ()
    mput = put
    
    mmodify :: (MyMap a -> MyMap a) -> MyState a ()
    mmodify x = modify x
    
    xnew :: s -> MyState s MyRef
    xnew val = do
        s <- mget
        let newRef = if M.null s then 0 else fst (M.findMax s) + 1
        mput $ M.insert newRef val s
        return newRef
    
    xset :: MyRef -> a -> MyState a () 
    xset ref val = modify $ M.insert ref val
    
    xget :: MyRef -> MyState a a
    xget ref = fmap (\s -> case M.lookup ref s of Just v -> v) get
    
    test :: MyState Int Int
    test = do
      x1 <- xnew 2
      xset x1 3
      x2 <- xget x1
      y1 <- xnew 10
      xset y1 20
      y2 <- xget y1
      return (x2 + y2)
    
    main = print $ xrun test
    

    可以实现模块中的所有功能和 >>= / return ,而无需使用 Control.Monad 中保留签名的库存实现 .

    这里是:

    module MyState (State, get, put, modify, evalState) where
    
    newtype State s a = State (s -> (a, s))
    
    evalState :: State s a -> s -> a
    evalState (State f) = fst . f
    
    instance Monad (State s) where
        return a = State $ \s -> (a, s)
        State f >>= g = State $ \s -> 
            case f s of 
                (a', s') -> case g a' of 
                    State h -> h s'
    
    instance Functor (State s) where
        fmap f (State g) = State $ 
            \s -> case g s of (a, s) -> (f a, s) 
    
    get :: State s s
    get = State (\s -> (s, s))
    
    put :: s -> State s ()
    put s = State $ \_ -> ((), s)
    
    modify :: (s -> s) -> State s ()
    modify f = get >>= put . f
    

    将其保存到 MyState.hs 并将 import Control.Monad.State 替换为 import MyState .

  • 2

    使用 StateStateT 可以模拟它( State 仅允许1个值) . 最简单的方法是使用 Map

    do
      put empty
      set "x1" 3  
      x2 <-  getKey "x1"
      set "y1" 20
      y2 <-  getKey "y1"
      return (x2 + y2)
        where
          getKey k = fromJust . (lookup k) `fmap` get
          set = modify replace
          replace d k m = if k `member` m then update (\_ -> Just d) k m
                          else insert k d m
    
  • 6

    StateT for a tuple怎么样?

    flip evalState (2, 10) $ do
      modify $ \(_, y) -> (3, y)
      x2 <- fst <$> get
      modify $ \(x, _) -> (x, 20)
      y2 <- snd <$> get
      return (x2 + y2)
    

    如果你真的想要可变的单元格,我建议使用ST,STM或IO而不是StateT . 使用StateT在异构映射中实现从增加自然到对象的实现似乎是可能的,但可能有点尴尬 .

  • 5

    这允许多个值,但是_218800_建议 Dynamic .

    import Data.Dynamic
    import Data.Maybe
    import Control.Monad.State
    import Data.Map as M
    
    newtype Ref a = Ref {ref :: Int}
    
    type MutState = State (Int, Map Int Dynamic)
    
    val :: Typeable a => Ref a -> MutState a
    val r = snd `fmap` get >>= 
            return . fromJust . (>>= fromDynamic) .  M.lookup (ref r)
    
    new :: Typeable a => a -> MutState (Ref a)
    new a = do
      (curr, binds) <- get
      put (curr + 1, M.insert (curr + 1) (toDyn a) binds)
      return . Ref $ curr + 1
    
    set :: Typeable a => Ref a -> a -> MutState ()
    set (Ref i) a = do
      (c, m) <- get
      put (c, M.insert i (toDyn a) m)
    
    runMut :: MutState a -> a
    runMut = flip evalState (0, M.fromList [])
    

    然后使用它

    default (Int) -- too lazy for signatures :)
    test :: Int
    test = runMut $ do
      x1 <- new 2
      set x1 3
      x2 <- val x1
      y1 <- new 10
      set y1 20
      y2 <- val y1
      return (x2 + y2)
    

    Ref s基本上是 Int s,附有一些类型信息, val 将查找相应的 Dynamic 并尝试强制它进入正确的类型 .

    如果这是真正的代码,您应该隐藏 RefMutState 的实现 . 为方便起见,如果你想要一个安全的实现,我已经 fromJust 返回了 val bur我想你可以将 StateMaybe monads分层来处理未绑定的变量 .

    如果您担心类型限制,如上所示,它们可以简单地推导出来 .

相关问题