Why runState signature has only state argument?

2019-04-26 04:13发布

A real-life example: If I'm in a good mood('good state'), when manager asks me about estimates, I give him a solid answer, but dares he to do that 3 times in a row, without some free snacks in between, my mood changes(I get to 'bad state') and the next 3 times he approaches I ask him not to disturb me with any of his nonsense.

Here's a log of my usual day:

                             [ 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 ]
...

Now this model of me at work seems to fit the State Monad.

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

But how do I do this? The signature has room for a state, which in my case is (Mood,Patience), and not for input (ESTIMATE or Cookies). It's like I'm supposed to answer without even listening!

So my question is: How do I make not only Stateful but also Argumentful computation with State monad of Haskell?

2条回答
仙女界的扛把子
2楼-- · 2019-04-26 04:42

A stateful computation gets an input, a state and returns an output and a new state. So the type will be input -> state -> (state, output).

The runState is just a partially applied stateful computation that has already taken its inputs.

Note also that when you compose stateful functions (i.e. when you use the >>= bind operator or do notation) you do exactly this: you supply the inputs as expression and the bind is responsible for passing around only the state.

You can call get without using its return value, but then it gets lost. If you want to make use of it you have to use value <- get and then provide the value as an explicit input for the next stateful computation. The bind only comes into play when passing the state around.


Practical example: consider the function:

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

The doStuff type has exactly the pattern input -> state -> (state, output). But the input part is represented by the x argument. Once you provide x you get something of type state -> (state, output) which is exactly what runState represents.

So you don't actually need the arguments for stateful actions because you can partially apply them beforehand to obtain "pure stateful computations that have no input" (those are scary quotes).

查看更多
smile是对你的礼貌
3楼-- · 2019-04-26 04:43

It sounds like what you're looking for is not State but StateT, a monad transformer that adds statefulness to an existing monad.

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

Given a state of type s , a StateT s m a action returns an m action that, when run, produces a result and a new state. StateT s is an instance of MonadTrans:

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

Further, if m is a Monad, then so is StateT s m.

So if you want to take "input" within some context (e.g., IO), you are free to do so:

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

For IO in particular, it's usually better to use liftIO, which can lift through a whole stack of transformers.

查看更多
登录 后发表回答