为什么 runState 签名只有 state 参数?

Why runState signature has only state argument?

一个真实的例子:如果我心情好('good state'),当经理问我估算时,我给了他一个可靠的答案,但他敢吗连续做 3 次 ,中间没有免费零食,我的情绪发生了变化(我达到了 'bad state'),接下来的 3 次他接近我,我要求他不要用他的任何废话来打扰我。

这是我平常一天的日志:

                             [ Mood: Good, Patience: 3 ]  -- 11:00 am, I'm happy
ESTIMATE -> "bla bla 6",     [ Mood: Good, Patience: 2 ]
ESTIMATE -> "bla bla 1",     [ Mood: Good, Patience: 1 ]
Cookies! -> "",              [ Mood: Good, Patience: 3 again! ]
ESTIMATE -> "bla bla 7",     [ Mood: Good, Patience: 2 ]
ESTIMATE -> "bla bla 2",     [ Mood: Good, Patience: 1 ]
ESTIMATE -> "bla bla 9",     [ Mood: BAD , Patience: -2 ] -- Enough!
ESTIMATE -> "Need a break!"  [ Mood: BAD , Patience: -1 ]
ESTIMATE -> "Deploynig!",    [ Mood: BAD , Patience: 0 ]
ESTIMATE -> "Lunch time!",   [ Mood: Good, Patience: 3 ]  -- Ok he needs me..
ESTIMATE -> "bla bla 6",     [ Mood: Good, Patience: 2 ]
...

现在我工作时的这个模型似乎适合 State Monad。

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

但是我该怎么做呢?签名有一个状态空间,在我的例子中是 (Mood,Patience),而不是输入空间(ESTIMATECookies)。好像连听都不听就得回答!

所以我的问题是:如何使用 Haskell 的 State monad 进行有状态计算和论证计算?

有状态计算得到一个输入、一个状态,return一个输出和一个新状态。所以类型将是 input -> state -> (state, output).

runState 只是一个部分应用的状态计算,它已经接受了它的输入。

另请注意,当您编写有状态函数时(即当您使用 >>= 绑定运算符或 do 符号时),您正是这样做的:您提供输入作为表达式,绑定负责仅传递 state.

您可以在不使用其 return 值的情况下调用 get,但它会丢失。如果你想使用它,你必须使用 value <- get 然后提供 value 作为下一个有状态计算的显式输入。绑定仅在传递状态时起作用。


实际例子:考虑函数:

doStuff :: Int -> State Int Int
doStuff x = do
    val <- get
    put $ val+x+1
    return 0

doStuff 类型恰好具有模式 input -> state -> (state, output)。但是 input 部分由 x 参数表示。 一旦你提供 x 你会得到类型 state -> (state, output) 的东西,这正是 runState 所代表的。

所以您实际上不需要状态操作的参数,因为您可以预先部分应用它们以获得 "pure stateful computations that have no input"(这些引号很吓人)。

听起来您要找的不是 State,而是 StateT,一个 monad 转换器,它为现有的 monad 添加了状态。

newtype StateT s m a = StateT (s -> m (a, s))

给定类型 s 的状态,一个 StateT s m a 动作 returns 一个 m 动作,当 运行 时产生一个结果和一个新的状态。 StateT sMonadTrans:

的实例
instance MonadTrans (StateT s) where
  --lift :: Monad m => m a -> StateT s m a
  lift ma = StateT $
    \s -> ma >>= \a -> pure (a, s)

此外,如果 mMonad,那么 StateT s m 也是。

因此,如果您想在某些情况下使用 "input"(例如,IO),您可以这样做:

do
  s <- get
  input <- lift getLine
  when (annoying input && annoyed s) $ put Angry

特别是IO,通常使用liftIO更好,它可以举起一整堆变压器。