Scalaz state monad examples

前端 未结 3 875
北恋
北恋 2020-12-02 05:12

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

3条回答
  •  难免孤独
    2020-12-02 05:33

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

提交回复
热议问题