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

7
一个实际的例子:如果我处于良好的心情(“良好状态”)时,经理问我关于估计的问题,我会给他一个坚定的答案,但如果他连续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) } 

但是我该如何做到这一点呢?签名中有一个状态变量,而在我的情况下它是 (心情,耐心),而不是输入变量(ESTIMATECookies)。这就好像我必须在没有听取的情况下回答。

所以我的问题是:如何使用Haskell的State单子,使计算既具有状态又具有参数?

2个回答

8
一个有状态的计算接收一个输入、一个状态并返回一个输出和一个新状态,因此其类型将是input -> state -> (state, output)runState只是一个已经使用了它的输入部分的有状态计算。
还要注意,当你组合有状态的函数(即当你使用>>=绑定运算符或do表示法时),你正是这样做的:你将表达式作为输入并且绑定负责传递状态
你可以调用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所代表的。 因此,您实际上不需要状态动作的参数,因为可以事先部分地应用它们以获得“没有输入的纯状态动作”(这些引号很可怕)。

2

看起来你需要的不是 State 而是 StateT,它是一种 Monad Transformer,可以将状态添加到现有的 Monad 中。

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

给定一个类型为 s 的状态,一个 StateT s m a 操作返回一个 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)

此外,如果m是一个Monad,那么StateT s m也是一个Monad
因此,如果你想在某个上下文(例如IO)中获取“输入”,你可以自由地这样做:
do
  s <- get
  input <- lift getLine
  when (annoying input && annoyed s) $ put Angry

对于特别涉及到IO的情况,通常最好使用liftIO,它可以穿过整个transformer栈。


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