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