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]()))