I haven't seen many examples of the scalaz state monad. There is this example but it is hard to understand and there is only one other question on stack overflow it seems.
I'm going to post a few examples I've played with but I would welcome additional ones. Also if somebody can provide example on why init
, modify
, put
and gets
are used for that would be great.
Edit: here is an awesome 2 hours presentation on the state monad.
The state monad is a built in monad in Haskell that allows for chaining of a state variable (which may be arbitrarily complex) through a series of function calls, to simulate stateful code. It is defined as: newtype State s a = State { runState :: (s -> (a,s)) }
Other monads have their own "run" functions, i.e. runReader for the Reader monad, runState for the State monad, etc. Show activity on this post. Monads are not considered pure or impure. They're totally unrelated concepts.
I stumbled on an interesting blog post Grok Haskell Monad Transformers from sigfp that has an example of applying two state monads through a monad transformer. Here is a scalaz translation.
The first example shows a State[Int, _]
monad:
val test1 = for {
a <- init[Int]
_ <- modify[Int](_ + 1)
b <- init[Int]
} yield (a, b)
val go1 = test1 ! 0
// (Int, Int) = (0,1)
So I have here an example of using init
and modify
. After playing with it a bit, init[S]
turns out to be really convenient to generate a State[S,S]
value, but the other thing it allows is to access the state inside the for comprehension. modify[S]
is a convenient way to transform the state inside the for comprehension. So the example above can be read as:
a <- init[Int]
: start with an Int
state, set it as the value wrapped by the State[Int, _]
monad and bind it to a
_ <- modify[Int](_ + 1)
: increment the Int
stateb <- init[Int]
: take the Int
state and bind it to b
(same as for a
but now the state is incremented)State[Int, (Int, Int)]
value using a
and b
.The for comprehension syntax already makes it trivial to work on the A
side in State[S, A]
. init
, modify
, put
and gets
provide some tools to work on the S
side in State[S, A]
.
The second example in the blog post translates to:
val test2 = for {
a <- init[String]
_ <- modify[String](_ + "1")
b <- init[String]
} yield (a, b)
val go2 = test2 ! "0"
// (String, String) = ("0","01")
Very much the same explanation as test1
.
The third example is more tricky and I hope there is something simpler that I have yet to discover.
type StateString[x] = State[String, x]
val test3 = {
val stTrans = stateT[StateString, Int, String]{ i =>
for {
_ <- init[String]
_ <- modify[String](_ + "1")
s <- init[String]
} yield (i+1, s)
}
val initT = stateT[StateString, Int, Int]{ s => (s,s).pure[StateString] }
for {
b <- stTrans
a <- initT
} yield (a, b)
}
val go3 = test3 ! 0 ! "0"
// (Int, String) = (1,"01")
In that code, stTrans
takes care of the transformation of both states (increment and suffix with "1"
) as well as pulling out the String
state. stateT
allows us to add state transformation on an arbitrary monad M
. In this case the state is an Int
that is incremented. If we called stTrans ! 0
we would end up with M[String]
. In our example, M
is StateString
, so we'll end up with StateString[String]
which is State[String, String]
.
The tricky part here is that we want to pull out the Int
state value out from stTrans
. This is what initT
is for. It just creates an object that gives access to the state in a way we can flatMap with stTrans
.
Edit: Turns out all of that awkwardness can be avoided if we truly reused test1
and test2
which conveniently store the wanted states in the _2
element of their returned tuples:
// same as test3:
val test31 = stateT[StateString, Int, (Int, String)]{ i =>
val (_, a) = test1 ! i
for (t <- test2) yield (a, (a, t._2))
}
Here is a very small example on how State
can be used:
Let's define a small "game" where some game units are fighting the boss (who is also a game unit).
case class GameUnit(health: Int)
case class Game(score: Int, boss: GameUnit, party: List[GameUnit])
object Game {
val init = Game(0, GameUnit(100), List(GameUnit(20), GameUnit(10)))
}
When the play is on we want to keep track of the game state, so let's define our "actions" in terms of a state monad:
Let's hit the boss hard so he loses 10 from his health
:
def strike : State[Game, Unit] = modify[Game] { s =>
s.copy(
boss = s.boss.copy(health = s.boss.health - 10)
)
}
And the boss can strike back! When he does everyone in a party loses 5 health
.
def fireBreath : State[Game, Unit] = modify[Game] { s =>
val us = s.party
.map(u => u.copy(health = u.health - 5))
.filter(_.health > 0)
s.copy(party = us)
}
Now we can compose these actions into play
:
def play = for {
_ <- strike
_ <- fireBreath
_ <- fireBreath
_ <- strike
} yield ()
Of course in the real life the play will be more dynamic, but it is food enough for my small example :)
We can run it now to see the final state of the game:
val res = play.exec(Game.init)
println(res)
>> Game(0,GameUnit(80),List(GameUnit(10)))
So we barely hit the boss and one of the units have died, RIP.
The point here is the composition.
State
(which is just a function S => (A, S)
) allows you to define actions that produce results and as well manipulate some state without knowing too much where the state is coming from.
The Monad
part gives you composition so your actions can be composed:
A => State[S, B]
B => State[S, C]
------------------
A => State[S, C]
and so on.
P.S. As for differences between get
, put
and modify
:
modify
can be seen as get
and put
together:
def modify[S](f: S => S) : State[S, Unit] = for {
s <- get
_ <- put(f(s))
} yield ()
or simply
def modify[S](f: S => S) : State[S, Unit] = get[S].flatMap(s => put(f(s)))
So when you use modify
you conceptually use get
and put
, or you can just use them alone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With