Scalaz state monad examples

前端 未结 3 874
北恋
北恋 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 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 state
    • b <- init[Int]: take the Int state and bind it to b (same as for a but now the state is incremented)
    • yield a 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))
    }
    

提交回复
热议问题