{"id":23902749,"url":"https://github.com/mtumilowicz/scala-cats-functional-dependency-injection-workshop","last_synced_at":"2025-07-18T00:09:01.000Z","repository":{"id":37538394,"uuid":"450646914","full_name":"mtumilowicz/scala-cats-functional-dependency-injection-workshop","owner":"mtumilowicz","description":"Introduction into functional dependency injection with Reader monad.","archived":false,"fork":false,"pushed_at":"2023-02-18T21:03:03.000Z","size":81,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-11T00:13:42.364Z","etag":null,"topics":["cats","cats-effect","dependency-injection","effect","functional-dependency-injection","functional-programming","kleisli","kleisli-arrows","monad-transformers","reader","reader-monad","workshop","workshop-materials","workshops"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mtumilowicz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-01-21T21:36:19.000Z","updated_at":"2024-05-19T12:44:00.000Z","dependencies_parsed_at":"2025-04-11T00:13:43.143Z","dependency_job_id":"2da4db00-6722-4587-b6fd-b78585700516","html_url":"https://github.com/mtumilowicz/scala-cats-functional-dependency-injection-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mtumilowicz/scala-cats-functional-dependency-injection-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-functional-dependency-injection-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-functional-dependency-injection-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-functional-dependency-injection-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-functional-dependency-injection-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtumilowicz","download_url":"https://codeload.github.com/mtumilowicz/scala-cats-functional-dependency-injection-workshop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-functional-dependency-injection-workshop/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265683491,"owners_count":23810854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cats","cats-effect","dependency-injection","effect","functional-dependency-injection","functional-programming","kleisli","kleisli-arrows","monad-transformers","reader","reader-monad","workshop","workshop-materials","workshops"],"created_at":"2025-01-04T22:50:17.844Z","updated_at":"2025-07-18T00:09:00.992Z","avatar_url":"https://github.com/mtumilowicz.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://app.travis-ci.com/mtumilowicz/scala-cats-functional-dependency-injection-workshop.svg?branch=master)](https://app.travis-ci.com/mtumilowicz/scala-cats-functional-dependency-injection-workshop)\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n# scala-cats-functional-dependency-injection-workshop\n\n* references\n    * https://github.com/kitlangton/zio-from-scatch\n    * https://medium.com/@alexander.zaidel/composing-functions-with-reader-monad-f3e471958e2a\n    * https://fares.codes/posts/cats-kleisli/\n    * https://stackoverflow.com/questions/61899370/when-should-one-use-a-kleisli\n    * https://medium.com/@supermanue/understanding-kleisli-in-scala-9c42ec1a5977\n    * https://typelevel.org/cats/datatypes/kleisli.html\n    * https://blog.softwaremill.com/kleisli-category-from-theory-to-cats-fbd140bf396e\n    * https://blog.buildo.io/monad-transformers-for-the-working-programmer-aa7e981190e7\n    * https://blog.softwaremill.com/monad-transformers-and-cats-3-tips-for-beginners-196fabe58daa\n    * [Monad transformers down to earth by Gabriele Petronella](https://www.youtube.com/watch?v=jd5e71nFEZM)\n    * https://github.com/zio/izumi-reflect\n    * https://www.baeldung.com/scala/type-information-at-runtime\n    * https://dotty.epfl.ch/api/scala/reflect/ClassTag.html\n\n## preface\n* goals of this workshop:\n    * showing the functional approach to dependency injection\n    * practicing using effects for modelling domain\n    * example of conditionally added methods to the type (based on the type)\n* task: rewrite the application from `app` package using effects\n    * answers: `answers` package\n* it can be worthwhile to take a look at\n    * type projection: https://github.com/mtumilowicz/scala213-functional-programming-functor-monoid-monad-workshop\n        * https://github.com/typelevel/kind-projector\n    * conditionally adding methods (`\u003c:\u003c`): https://github.com/mtumilowicz/scala-cats-implicit-workshop\n    * Reader: https://github.com/mtumilowicz/java11-category-theory-reader-functor\n    * Kleisli\n        * https://github.com/mtumilowicz/java11-category-theory-kleisli-category\n        * https://github.com/mtumilowicz/scala212-category-theory-kleisli-writer-category-functor\n    * heterogenous map (`Has`) and effects: https://github.com/mtumilowicz/scala-http4s-zio-fs2-doobie-workshop\n\n## Reader\n* main purpose: compose functions and delay dependency injection phase until the later moment\n* drawback: combining Reader together with other monads requires to write a bit of boilerplate\n    ```\n    def validateUser(name: Name, age: Age): Reader[Env, Try[User]] =\n        for {\n          validatedNameTry \u003c- validateName(name)\n          validatedAgeTry \u003c- validateAge(age)\n        } yield for {\n            validatedName \u003c- validatedNameTry\n            validatedAge \u003c- validatedAgeTry\n          } yield User(validatedName, validatedAge)\n\n    private def validateName(name: Name): Reader[Env, Try[Name]]\n\n    private def validateAge(age: Age): Reader[Env, Try[Age]]\n    ```\n* solution: `ReaderT` (monad transformer)\n    ```\n    def validateUser(name: Name, age: Age): ReaderT[Try, Env, User] =\n      for {\n        validatedName \u003c- validateName(name)\n        validatedAge \u003c- validateAge(age)\n      } yield User(validatedName, validatedAge)\n\n    private def validateName(name: Name): ReaderT[Try, Env, Name]\n\n    private def validateAge(age: Age): ReaderT[Try, Env, Age]\n    ```\n* can also be used for passing a \"Diagnostic Context\"\n    * example: add `requestId` to every log message in your code base\n        * make `requestId` accessible in every function\n    * solution\n        ```\n        def saveUser(user: User): = ReaderT[Try, Context, User] { context =\u003e\n          logger.debug(s\"reqId: ${context.requestId} saveUser ${user}\")\n          // saving logic goes here\n        }\n\n        def createUser(name: Name, age: Age): Try[User] =\n          (for {\n            user \u003c- validateUser(name, age)\n            savedUser \u003c- UserRepository.saveUser(user)\n          } yield savedUser).run(Context(generateReqId()))\n        ```\n## monad transformers\n* Monads do not compose, at least not generically\n    * note that Functors compose, so we have problem only with `flatMap`\n* problem\n    ```\n    def findUserById(id: Long): Future[Option[User]] = ???\n    def findAddressByUser(user: User): Future[Option[Address]] = ???\n\n    def findAddressByUserId(id: Long): Future[Option[Address]] =\n      for {\n        user    \u003c- findUserById(id) // user has type: Option[User]\n        address \u003c- findAddressByUser(user) // error: type mismatch;\n      } yield address\n    ```\n    or in other words\n    ```\n    val fl: Future[List[Int]] = ??\n\n    fl.flatMap(list =\u003e list.flatMap(f)) // two maps, one function\n    ```\n* solution\n    ```\n    case class FutureOpt[A](value: Future[Option[A]]) {\n\n      def map[B](f: A =\u003e B): FutureOpt[B] =\n        FutureOpt(value.map(optA =\u003e optA.map(f)))\n      def flatMap[B](f: A =\u003e FutureOpt[B]): FutureOpt[B] =\n        FutureOpt(value.flatMap(opt =\u003e opt match {\n          case Some(a) =\u003e f(a).value\n          case None =\u003e Future.successful(None)\n        }))\n    }\n\n    def findAddressByUserId(id: Long): Future[Option[Address]] =\n      (for {\n        user    \u003c- FutOpt(findUserById(id))\n        address \u003c- FutOpt(findAddressByUser(user))\n      } yield address).value\n    ```\n* and suppose we want to have wrapper for `List[Future]`\n    ```\n    case class ListOpt[A](value: List[Option[A]]) {\n\n     def map[B](f: A =\u003e B): ListOpt[B] =\n        ListOpt(value.map(optA =\u003e optA.map(f)))\n     def flatMap[B](f: A =\u003e ListOpt[B]): ListOpt[B] =\n        ListOpt(value.flatMap(opt =\u003e opt match {\n          case Some(a) =\u003e f(a).value\n          case None =\u003e List(None)\n        }))\n    }\n    ```\n* conclusion\n    * we don’t need to know anything specific about the \"outer\" Monad\n    * we have to know something about \"inner\" Monad\n        * see how we destructured the `Option`?\n    * we could abstract over the \"outer\" Monad\n        * usually named `OptionT`\n        * `OptionT[F, A]` is a flat version of `F[Option[A]]` which is a Monad itself\n\n## Kleisli\n* `Kleisli[F[_], A, B]` is just a wrapper around the function `A =\u003e F[B]`\n* Kleisli can be viewed as the monad transformer for functions\n    * allows the composition of functions where the return type is a monadic\n    * problem\n        ```\n        val parse: String =\u003e Option[Int] =\n          s =\u003e if (s.matches(\"-?[0-9]+\")) Some(s.toInt) else None\n\n        val reciprocal: Int =\u003e Option[Double] =\n          i =\u003e if (i != 0) Some(1.0 / i) else None\n\n        // you cannot compose it, you have to flatMap them\n        def parseAndReciprocal(s: String): Option[Double] = parse(s).flatMap(reciprocal)\n        ```\n    * solution (wrapping functions declared above)\n        ```\n        val parseKleisli: Kleisli[Option,String,Int] =\n          Kleisli(parse)\n\n        val reciprocalKleisli: Kleisli[Option, Int, Double] =\n          Kleisli(reciprocal)\n\n        val parseAndReciprocalKleisli = parseKleisli andThen reciprocalKleisli\n        ```\n* problem: several functions depend on some environment and we want a nice way to compose these functions to ensure\nthey all receive the same environment\n* example\n    ```\n    val makeDB: Config =\u003e IO[Database]\n    val makeHttp: Config =\u003e IO[HttpClient]\n    val makeCache: Config =\u003e IO[RedisClient]\n\n    def program(config: Config) = for {\n      db \u003c- makeDB(config)\n      http \u003c- makeHttp(config)\n      cache \u003c- makeCache(config)\n      ...\n    } yield someResult\n* solution\n    ```\n    val makeDB: Kleisli[IO, Config, Database]\n    val makeHttp: Kleisli[IO, Config, HttpClient]\n    val makeCache: Kleisli[IO, Config, RedisClient]\n\n    val program: Kleisli[IO, Config, Result] = for {\n      db \u003c- makeDB\n      http \u003c- makeHttp\n      cache \u003c- makeCache\n      ...\n    } yield someResult\n    ```\n## izumi Tag\n* is a fast, lightweight, portable and efficient alternative for TypeTag from scala-reflect\n* compiles faster, runs a lot faster than scala-reflect and is fully immutable and thread-safe\n* why we need tags?\n  * type erasure - compiler removes all generic type information at compile-time, leaving \n  this information missing at runtime\n  * tags are solutions to get the type information of the erased type at runtime\n* example\n  * problem: heterogenous map of dependencies\n    * service type -\u003e service instance\n  * solution\n    ```\n    final case class Has[A](map: Map[String, Any])\n\n    implicit class HasOps[Self \u003c: Has[_]](self: Self) {\n    \n      def get[A](implicit ev: Self \u003c:\u003c Has[A], tag: Tag[A]): A =\n        self.map(tag.toString).asInstanceOf[A]\n  \n      def ++[That \u003c: Has[_]](that: That): Self with That =\n        Has(self.map ++ that.map).asInstanceOf[Self with That]\n    }\n    ```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-cats-functional-dependency-injection-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtumilowicz%2Fscala-cats-functional-dependency-injection-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-cats-functional-dependency-injection-workshop/lists"}