State Monad有多个州的价值观

时间:2013-10-07 14:27:54

标签: haskell monads state-monad

请考虑以下事项:

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 个答案:

答案 0 :(得分:6)

这允许多个值,但它更加毛茸茸:)这很好地简化了Daniel对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返回State bur我想你可以将Maybe和{{1}} monad分层来处理未绑定的变量。

如果您担心类型限制,如上所示,它们很简单。

答案 1 :(得分:5)

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

以下是使用该实现的任务示例。没有使用可变性。请注意,您的示例按原样粘贴,只需添加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

答案 2 :(得分: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

答案 3 :(得分:2)

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在异构地图上实现从增加自然到对象的实现似乎是可能的,但可能有点尴尬。

相关问题