In Integrating State with Either (slide 88), given the pattern of State
layered under Either
, is there a recommended approach for adding another type of state, e.g., logging via something like Writer
? It seems the new state has to live between the existing State
and Either
in order to take advantage of the fail-fast behavior of Either
in flatMap
.
Below is a runnable example of the code from the presentation, adjusted to work on 2.11.8 with Scalaz 7.2.8. Is there an approach that can cleanly add the new monad transformer on top of the existing behavior, simplifying refactoring? Stacking StateT in Scalaz applies to stacking but doesn't deal with the ordering problem created by the fail-fast flatMap
behavior of Either
.
// Based on slide 88+ in https://speakerdeck.com/mpilquist/scalaz-state-monad
// Adjusted for Scala 2.11 (invariant A), Scalaz 7.2 (Pointed->Applicative) and Throwable on lhs of Either
object IntegratingStateAndEither {
import scalaz._
import scalaz.Scalaz._
import EitherT._
import scalaz.StateT.stateMonad
type QueryStateS[A] = State[QueryState, A]
type ET[F[_], A] = EitherT[F, Throwable, A]
type QueryStateES[A] = ET[QueryStateS, A]
object QueryStateES {
def apply[A](s: QueryStateS[Throwable \/ A]): QueryStateES[A] = EitherT(s)
def liftE[A](e: Throwable \/ A): QueryStateES[A] = apply(Applicative[QueryStateS].point(e))
def liftS[A](s: QueryStateS[A]): QueryStateES[A] = MonadTrans[ET].liftM(s)
}
def runQuery(s: String, m: Model): QueryStateES[QueryResult] = for {
query <- parseQuery(s)
res <- performQuery(query, m)
} yield res
def parseQuery(s: String): QueryStateES[StatsQuery] =
QueryStateES.liftE(new Exception("TODO parse").left)
def performQuery(q: StatsQuery, m: Model): QueryStateES[QueryResult] =
QueryStateES.liftE(new Exception("TODO perform").left)
// Just examples that do nothing
case class Model()
case class StatsQuery()
case class QueryResult()
case class QueryState()
def test = runQuery("a + b", Model()).run.run(QueryState())
}
To answer your specific example about logging you could do something like this:
object LayeringReaderWriterStateWithEither {
// Based on slide 88+ in https://speakerdeck.com/mpilquist/scalaz-state-monad
// Adjusted for Scala 2.11 (invariant A), Scalaz 7.2 (Pointed->Applicative) and Throwable on lhs of Either
object IntegratingStateAndEither {
import scalaz._
import scalaz.Scalaz._
import EitherT._
type QueryStateS[A] = ReaderWriterState[List[String], String, QueryState, A]
type ET[F[_], A] = EitherT[F, Throwable, A]
type QueryStateES[A] = ET[QueryStateS, A]
object QueryStateES {
def apply[A](s: QueryStateS[Throwable \/ A]): QueryStateES[A] = EitherT(s)
def liftE[A](e: Throwable \/ A): QueryStateES[A] = apply(Applicative[QueryStateS].point(e))
def liftS[A](s: QueryStateS[A]): QueryStateES[A] = MonadTrans[ET].liftM(s)
def log(msg: String): QueryStateES[Unit] = liftS {
ReaderWriterState[List[String], String, QueryState, Unit] {
case (r, s) => (msg.format(r, s), (), s).point[Id]
}
}
}
def runQuery(s: String, m: Model): QueryStateES[QueryResult] = for {
_ ← log("Starting")
query <- parseQuery(s)
_ ← log(s"Got a query: $query")
res <- performQuery(query, m)
} yield res
def log(msg: String): QueryStateES[Unit] =
QueryStateES.log(msg)
def parseQuery(s: String): QueryStateES[StatsQuery] =
QueryStateES.liftE(new Exception(s"TODO parse $s").left)
def performQuery(q: StatsQuery, m: Model): QueryStateES[QueryResult] =
QueryStateES.liftE(new Exception(s"TODO perform $q in $m").left)
// Just examples that do nothing
case class Model()
case class StatsQuery()
case class QueryResult()
case class QueryState()
def test = runQuery("a + b", Model()).run.run(Nil, QueryState())
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With