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.
I assume, scalaz 7.0.x and the following imports (look at answer history for scalaz 6.x):
import scalaz._
import Scalaz._
The state type is defined as State[S, A]
where S
is type of the state and A
is the type of the value being decorated. The basic syntax to create a state value makes use of the State[S, A]
function:
// Create a state computation incrementing the state and returning the "str" value
val s = State[Int, String](i => (i + 1, "str"))
To run the state computation on a initial value:
// start with state of 1, pass it to s
s.eval(1)
// returns result value "str"
// same but only retrieve the state
s.exec(1)
// 2
// get both state and value
s(1) // or s.run(1)
// (2, "str")
The state can be threaded through function calls. To do this instead of Function[A, B]
, define Function[A, State[S, B]]]
. Use the State
function...
import java.util.Random
def dice() = State[Random, Int](r => (r, r.nextInt(6) + 1))
Then the for/yield
syntax can be used to compose functions:
def TwoDice() = for {
r1 <- dice()
r2 <- dice()
} yield (r1, r2)
// start with a known seed
TwoDice().eval(new Random(1L))
// resulting value is (Int, Int) = (4,5)
Here is another example. Fill a list with TwoDice()
state computations.
val list = List.fill(10)(TwoDice())
// List[scalaz.IndexedStateT[scalaz.Id.Id,Random,Random,(Int, Int)]]
Use sequence to get a State[Random, List[(Int,Int)]]
. We can provide a type alias.
type StateRandom[x] = State[Random,x]
val list2 = list.sequence[StateRandom, (Int,Int)]
// list2: StateRandom[List[(Int, Int)]] = ...
// run this computation starting with state new Random(1L)
val tenDoubleThrows2 = list2.eval(new Random(1L))
// tenDoubleThrows2 : scalaz.Id.Id[List[(Int, Int)]] =
// List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))
Or we can use sequenceU
which will infer the types:
val list3 = list.sequenceU
val tenDoubleThrows3 = list3.eval(new Random(1L))
// tenDoubleThrows3 : scalaz.Id.Id[List[(Int, Int)]] =
// List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))
Another example with State[Map[Int, Int], Int]
to compute frequency of sums on the list above. freqSum
computes the sum of the throws and counts frequencies.
def freqSum(dice: (Int, Int)) = State[Map[Int,Int], Int]{ freq =>
val s = dice._1 + dice._2
val tuple = s -> (freq.getOrElse(s, 0) + 1)
(freq + tuple, s)
}
Now use traverse to apply freqSum
over tenDoubleThrows
. traverse
is equivalent to map(freqSum).sequence
.
type StateFreq[x] = State[Map[Int,Int],x]
// only get the state
tenDoubleThrows2.copoint.traverse[StateFreq, Int](freqSum).exec(Map[Int,Int]())
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]
Or more succinctly by using traverseU
to infer the types:
tenDoubleThrows2.copoint.traverseU(freqSum).exec(Map[Int,Int]())
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]
Note that because State[S, A]
is a type alias for StateT[Id, S, A]
, tenDoubleThrows2 ends up being typed as Id
. I use copoint
to turn it back into a List
type.
In short, it seems the key to use state is to have functions returning a function modifying the state and the actual result value desired... Disclaimer: I have never used state
in production code, just trying to get a feel for it.
Additional info on @ziggystar comment
I gave up on trying using stateT
may be someone else can show if StateFreq
or StateRandom
can be augmented to perform the combined computation. What I found instead is that the composition of the two state transformers can be combined like this:
def stateBicompose[S, T, A, B](
f: State[S, A],
g: (A) => State[T, B]) = State[(S,T), B]{ case (s, t) =>
val (newS, a) = f(s)
val (newT, b) = g(a) apply t
(newS, newT) -> b
}
It's predicated on g
being a one parameter function taking the result of the first state transformer and returning a state transformer. Then the following would work:
def diceAndFreqSum = stateBicompose(TwoDice, freqSum)
type St2[x] = State[(Random, Map[Int,Int]), x]
List.fill(10)(diceAndFreqSum).sequence[St2, Int].exec((new Random(1L), Map[Int,Int]()))