Search code examples
scalastate-monadscala-cats

Sequencing a StateMonad in Cats


I just had a look into the cats library in scala, more specifically the State Monad.

As a toy example I wanted to create some logic that splits a potentially large string (the StringBuilder) and returns the split up String and the remaining StringBuilder:

object Splitter {
  def apply(maxSize: Int, overlap: Int): State[StringBuilder, String] = State(
    builder => {
      val splitPoint = math.min(builder.size, maxSize + overlap)
      (builder.drop(maxSize), builder.substring(0, splitPoint))
    }
  )
}

Running one step of the State monad works fine but I wanted to chain all the steps until the StringBuilder eventually is empty:

val stop = State.inspect((s: StringBuilder) => s.isEmpty)
Splitter(3, 2).untilM[Vector](stop).run(new StringBuilder("tarsntiars"))

However, this doesn't work as untilM is a member of the Monad trait and there are no implicit conversions in scope. What works is:

val monad = StateT.catsDataMonadForStateT[Eval, StringBuilder]
monad.untilM[List, String](Splitter(3, 2))(stop).run(new StringBuilder("tarsntiars"))

However, I think the shorter is much more readable so I am wondering why it doesn't work? Why does the usual MonadOps mechanism doesn't work here?


Solution

  • After SI-2712 was fixed, the Unapply workaround was removed from Cats: https://github.com/typelevel/cats/pull/1583. Now you need the -Ypartial-unification compiler flag (assuming you are using Scala 2.11 or 2.12) in order for State to be treated as a Monad.

    Scalaz still has the Unapply machinery so your code should work with Scalaz without the compiler flag.