【问题标题】:State Monad with multiple state values具有多个状态值的状态单子
【发布时间】:2013-10-07 14:27:53
【问题描述】:

考虑以下几点:

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 的实例,但我不确定细节,所以一个工作示例会很有帮助。

【问题讨论】:

  • 您的问题只是关于实现可变引用还是您还需要学习如何实现纯状态 monad?
  • 纯状态。不需要实际的可变实现。如果这已经存在,则可以链接到 hackage 页面。这个问题:hackage.haskell.org/package/mtl-2.1.2/docs/… 是一个似乎能够put 整个状态,而不是一个特定的“变量”
  • 在您的代码中,x1y1 似乎是变量,而 x2y2 似乎是值;这些必须有不同的类型,所以我觉得命名令人惊讶。你知道吗?
  • 我不知道,但是是的,我明白你的意思。命名很普通,但我现在不会改变它。
  • 不知道你说的普通是什么意思。在 Haskell 中,Integer 类型的变量与某种状态下的命名引用完全不同。我真的建议不要在那里给出令人困惑的名字。

标签: haskell monads state-monad


【解决方案1】:

这允许多个值,但它更复杂 :) 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)

Refs 基本上是 Ints 附加了一些类型信息,val 将查找适当的 Dynamic 并尝试将其强制为正确的类型。

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

如果您担心可键入的约束,如上所示,它们是微不足道的。

【讨论】:

  • 为什么要重新实现Dynamic
  • @DanielWagner 简化谢谢!你知道有一天我会发布一个答案,而你却不发表评论表明它是多么不必要地过于复杂;)
【解决方案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

可以实现模块和&gt;&gt;=/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

【讨论】:

  • 我也先得到了这个答案(被我否决了,然后被我删除了),但如果你仔细观察,你会发现克林顿想要跟踪该州的变量,而不仅仅是一个州,所以类似于ST monad。
  • 我认为像type MyState a b = State (M.Map Int a, Int) b 这样的东西会为他完成这项工作。我现在正在详细说明答案
  • 这就是我在下面写的。请注意,您不需要第二个 Int,您可以轻松(有效)获得 Map 中的最大索引。
【解决方案3】:

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

【讨论】:

  • 我不希望在地图中进行字符串查找。我知道我说过我不担心效率,但这似乎有点不合时宜。
  • @Clinton 如果您有 200 个 .. 值,则效率不高。只是O(log n)
【解决方案4】:

对于一个元组,StateT 怎么样?

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 的实现似乎是可能的,但可能有点尴尬。

【讨论】:

  • 有趣的是\(_, y) -&gt; (3, y)(first . const) 3 相同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-25
相关资源
最近更新 更多