Reader モナド
2025/01/11
ScalaCatsプログラミング関数型プログラミング
今回は Reader モナドに触れるということでモナドのすべての Reader モナドのページに載っているサンプルを Cats を使って実装してみた。 Cats を触り始めたばっかりなのでよりよい書き方があるかもしれないが、一旦動くところまで書けたのでコードだけ載せておく。
Cats MTL を使うともう少しわかりやすく書ける部分がありそうな気がしている。
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}
Haskell のコードと比べると次の点が異なっていた。
- Haskell の
asks
が Scala ではReaderT.ask[Id, _].map(...)
asks
相当の関数は提供されていない(?)
- Haskell の
mapM
が Scala ではtraverse
- 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
- 書き方がよくないのか Haskell のように型推論が上手く働かない 😢
働き始めてからこの手のコードを書く機会がまったく無くなってしまったので、手続き型でコードを書いた方がわかりやすいのはで?と思ってしまった。そのため、上記のコードと同じコードを Reader モナドを使わずに実装して比較してみようと思う。