Notes
This article was translated by GPT-5.2-Codex. The original is here.
This time I looked at the Reader monad and implemented the sample from the Reader monad page of All About Monads using Cats. I just started using Cats so there may be a better way to write it, but it works for now, so I’ll just post the code.
I think Cats MTL could make some parts more readable.
1import cats.Id2import cats.data.Kleisli.{ask, pure}3import cats.data.{Reader, ReaderT}4import cats.syntax.all.*56enum Template {7case Text(value: String)8case Variable(template: Template)9case Quote(template: Template)10case Include(template: Template, definitions: List[Definition])11case Compound(templates: List[Template])12}1314case class Definition(name: Template, value: Template)1516case class Environment(templates: List[(String, Template)], variables: List[(String, String)])1718object Template {1920def localVar(name: String, env: Environment): Option[String] =21env.variables.find(_._1 == name).map(_._2)2223def lookupTemplate(name: String, env: Environment): Option[Template] =24env.templates.find(_._1 == name).map(_._2)2526def addDefs(defs: List[(String, String)], env: Environment): Environment =27Environment(env.templates, defs ++ env.variables)2829def resolveDef(definition: Definition): Reader[Environment, (String, String)] =30for {31name <- resolve(definition.name)32value <- resolve(definition.value)33} yield (name, value)3435def resolve(template: Template): Reader[Environment, String] =36template match {37case Text(value) => pure(value)38case Variable(template) =>39for {40varName <- resolve(template)41varValue <- ask[Id, Environment].map(localVar(varName, _))42} yield varValue.getOrElse("")43case Quote(template) =>44for {45tmplName <- resolve(template)46body <- ask[Id, Environment].map(lookupTemplate(tmplName, _))47} yield body.map(_.toString).getOrElse("")48case Include(template, definitions) =>49for {50tmplName <- resolve(template)51body <- ask[Id, Environment].map(lookupTemplate(tmplName, _))52rendered <- body match {53case Some(bodyTmpl) => for {54defs <- definitions.traverse(resolveDef)55rendered <- ReaderT.local(addDefs(defs, _))(resolve(bodyTmpl))56} yield rendered57case None => ReaderT.pure[Id, Environment, String]("")58}59} yield rendered60case Compound(templates) =>61for {62resolved <- templates.traverse(resolve)63} yield resolved.mkString64}65}
Compared to the Haskell code, the following points were different.
- Haskell's
askscorresponds toReaderT.ask[Id, _].map(...)in Scala- There is no equivalent to
asks(maybe?)
- There is no equivalent to
- Haskell's
mapMcorresponds totraversein Scala- Prelude
1mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
- Traverse - cats-docs_2.13 2.12.0 javadoc
1def traverse[G[_], A, B](fa: F[A])(f: (A) => G[B])(implicit arg0: Applicative[G]): G[F[B]]
- Prelude
- Type inference doesn't work as well as in Haskell (maybe my style is not great) 😢
Since I rarely write this kind of code after starting work, I can’t help but think that procedural code might be clearer. So I plan to implement the same logic without the Reader monad and compare.