Haskell状态单子

6
State Monad的put函数是否会更新实际状态,还是只返回一个带有新值的新状态?我的问题是,State Monad是否可以在命令式设置中像“全局变量”一样使用?put是否修改了“全局变量”?
我的理解是,它不会修改初始状态,但是使用Monad接口,我们可以在计算之间传递新状态,保留初始状态。这个理解正确吗?如果不是,请纠正我。
3个回答

6

state 并没有什么神奇的地方。你可以像这样实现它:

newtype State s a = State {runState :: s -> (a, s)}

也就是说,一个 State s a(我们认为它是使用类型为 s 的状态来产生类型为 a 的结果的计算)只是一个接受状态并返回结果和新状态的函数。您应该尝试编写此定义的 Monad 实例以及 getput 的定义。实际定义更加通用:

type State s = StateT s Identity
newtype Identity a = Identity a
newtype StateT s m a = StateT {runStateT :: s -> m (a, s)}

这允许向其他单子运算中添加状态。也可以将状态转换器定义为“操作单子”。 Apfelmus 在某处有关于这些的教程。


6
答案在类型中。
newtype State s a = State {runState :: s -> (a, s)}

因此,状态实质上是一个函数,它接受一个参数's'(我们称之为状态),并返回一个元组(值,状态)。该单子可以如下实现。
instance Monad (State s) where
  return a = State $ \s -> (a,s)
  (State f) >>= h = State $ \s -> let (a,s') =  f s
                                  in (runState h a) s'

因此,您有一个作用于初始状态并输出值-状态元组的函数,以供合成中的下一个函数处理。
现在,put是以下函数。
put newState = State $ \s -> ((),newState)

这实际上设置了状态,将传递给组合中的下一个函数,下游函数将看到修改后的状态。事实上,State monad是完全纯净的(也就是说,没有任何东西被设置);只有向下游传递的内容发生了变化。换句话说,在像Haskell这样的纯语言中,State monad可以帮助你省去显式携带状态的麻烦。换句话说,State monad只提供了一个接口,隐藏了状态线程的细节(这是在WikiBooks和Learn you a Haskell中所称的)。下面的内容展示了它的作用。您可以使用get来设置value字段与state字段相同的值(请注意,我所说的“设置”是指输出而不是变量)。put通过传递给它的值获取状态,将其增加并使用此新值设置状态。
-- execState :: State s a -> s -> s
let x =  get >>= \x -> put (x+10)
execState x 10

上面的输出结果为20。
现在,让我们做以下操作。
execState (x >> x) 10

这将会输出30。第一个x通过put方法将状态设置为20。第二个x现在使用这个状态。此时的get将状态传递到值字段,现在它的值为20。现在,我们的put方法将获取这个值,增加10并将其设置为新状态。
因此,在纯净的上下文中,您拥有状态。希望这可以帮助您。

1

首先,状态并不是"全局"的;你可以有几个不同的状态单子在运行,每个都有自己独立的状态,它们不会相互干扰。(事实上,这可能是整个意图。) 如果你想让状态在整个程序中都是全局的,你必须把整个程序放入一个单一的状态单子中。

其次,调用put会改变随后对get的调用返回的结果。就是这样。它不会"改变"实际值本身。比如,如果你调用get 并把结果放到某个变量中,然后调用put,你的变量不会改变。即使状态是一个字典或其他东西,如果你要添加一个新键并put它,任何仍在查看旧字典副本的人仍将看到旧字典。这不是状态单子特有的;这只是Haskell的工作原理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接