{"id":19506799,"url":"https://github.com/calvinlfer/free-monad-coproduct-example","last_synced_at":"2026-07-22T02:33:49.278Z","repository":{"id":68736736,"uuid":"75774025","full_name":"calvinlfer/free-monad-coproduct-example","owner":"calvinlfer","description":"Free Monad composition using Monad Coproducts from FreeK","archived":false,"fork":false,"pushed_at":"2017-04-20T00:58:07.000Z","size":5,"stargazers_count":4,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-19T12:36:59.364Z","etag":null,"topics":["coproduct","free-monads","freek","interpreter","scala"],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/calvinlfer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2016-12-06T21:41:42.000Z","updated_at":"2018-09-25T16:20:38.000Z","dependencies_parsed_at":"2023-03-14T21:45:12.142Z","dependency_job_id":null,"html_url":"https://github.com/calvinlfer/free-monad-coproduct-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/calvinlfer/free-monad-coproduct-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Ffree-monad-coproduct-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Ffree-monad-coproduct-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Ffree-monad-coproduct-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Ffree-monad-coproduct-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/calvinlfer","download_url":"https://codeload.github.com/calvinlfer/free-monad-coproduct-example/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Ffree-monad-coproduct-example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35744650,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["coproduct","free-monads","freek","interpreter","scala"],"created_at":"2024-11-10T22:38:29.079Z","updated_at":"2026-07-22T02:33:49.252Z","avatar_url":"https://github.com/calvinlfer.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Free Monads with FreeK\nA demonstration on how to compose different instruction sets together using `Coproduct` minimizing boilerplate using \n`FreeK`\n\nLibraries used: \n- [Cats](http://typelevel.org/cats/datatypes/freemonad.html)\n- [FreeK](https://github.com/ProjectSeptemberInc/freek)\n\nIn the example shown [here](https://github.com/calvinlfer/free-monads-functional-web-apps/commit/aa14d27dd390f5478b0b37e670a99d58210f5e5e#diff-df2bbb8433e5fb4f61e883e5b5cd8ca6R36)\nwhich is based off Chris Myer's talk: [A Year Living Freely](https://www.youtube.com/watch?v=rK53C-xyPWw), we cheat and \nmake instruction sets inherit from a common instruction set. When you do this, it becomes easy to compose instruction \nsets due to the common instruction set so you build a big interpreter that takes in the common instruction trait and \ndispatches instructions to the little interpreters. You can see an example of this in action [here](https://github.com/calvinlfer/free-monads-functional-web-apps).\n\nIf you don't have the luxury of controlling the source of all instruction sets then you need to turn to `Coproducts`.\nRúnar talks about this concept [here](http://functionaltalks.org/2014/11/23/runar-oli-bjarnason-free-monad/). This \nproject is a demonstration similar to Rúnar's but using [FreeK](https://github.com/ProjectSeptemberInc/freek)'s work to \nminimize as much boilerplate as possible. \n\nWe have two instruction sets:\n- Logging\n    ```scala\n      sealed trait LogInstruction[Result]\n      case class Debug(message: String) extends LogInstruction[Unit]\n      case class Info(message: String) extends LogInstruction[Unit]\n      case class Warn(message: String) extends LogInstruction[Unit]\n    ```\n\n- Greeting\n    ```scala\n      sealed trait GreetingsInstruction[Result]\n      case class WhoAreYou(message: String) extends GreetingsInstruction[String]\n      case object Hello extends GreetingsInstruction[Unit]\n      case object Bye extends GreetingsInstruction[Unit]\n    ```\n\nWe want to be able to compose instructions from both instruction sets together into a single program.\n\n## Process\nDefine a `Coproduct` that mixes the instruction sets together with the help of FreeK\n```scala\nimport freek._\n  sealed trait LogInstruction[Result]\n  // ...\n  \n  sealed trait GreetingsInstruction[Result]\n  // ...\n  \n  type ApplicationInstruction = LogInstruction :|: GreetingsInstruction :|: NilDSL\n  val ApplicationInstruction = DSL.Make[ApplicationInstruction]\n```\n\nDefine smart constructors that lift both your instruction sets into the Coproduct instruction set using `Free`\n```scala\n  // smart constructors\n  def debug(message: String): Free[ApplicationInstruction.Cop, Unit] =\n    Debug(message).freek[ApplicationInstruction]\n\n  def info(message: String): Free[ApplicationInstruction.Cop, Unit] =\n    Info(message).freek[ApplicationInstruction]\n\n  def warn(message: String): Free[ApplicationInstruction.Cop, Unit] =\n    Warn(message).freek[ApplicationInstruction]\n\n  def whoAreYou(message: String): Free[ApplicationInstruction.Cop, String] =\n    WhoAreYou(message).freek[ApplicationInstruction]\n\n  def hello: Free[ApplicationInstruction.Cop, Unit] =\n    Hello.freek[ApplicationInstruction]\n\n  def bye: Free[ApplicationInstruction.Cop, Unit] =\n    Bye.freek[ApplicationInstruction]\n```\n\nYou can write your interpreters for each instruction set as usual\n\n- Logging \n    ```scala\n      // Log interpreter implementation\n      val logInterpreter = new (LogInstruction ~\u003e Id) {\n        override def apply[A](fa: LogInstruction[A]): Id[A] = fa match {\n          case Debug(message) =\u003e\n            println(s\"DEBUG: $message\")\n            ()\n          case Warn(message) =\u003e\n            println(s\"WARN: $message\")\n            ()\n          case Info(message) =\u003e\n            println(s\"INFO: $message\")\n            ()\n        }\n      }\n    ```\n    \n- Greeting\n    ```scala\n      val greetingsInterpreters = new (GreetingsInstruction ~\u003e Id) {\n        override def apply[A](fa: GreetingsInstruction[A]): Id[A] = fa match {\n          case WhoAreYou(message: String) =\u003e\n            println(message)\n            val userInput = scala.io.StdIn.readLine()\n            userInput\n    \n          case Hello =\u003e\n            println(\"Hello!\")\n            ()\n    \n          case Bye =\u003e\n            println(\"Bye!\")\n            ()\n        }\n      }\n    ```\n\nYou can write your program using instructions from both instruction sets using the smart constructors. Since they have \nbeen lifted into Free of the Coproduct, you can compose instructions from both instruction sets\n\n```scala\nval instructions = for {\n_   \u003c- debug(\"beginning program\")\n_   \u003c- hello\nyou \u003c- whoAreYou(\"Enter your name\")\n_   \u003c- info(s\"Hello $you\")\n_   \u003c- warn(\"Exiting program\")\n} yield ()\n```\n\nThese are just instructions that haven't been executed yet, they just describe what we would like to do. Let's look at \nhow we can run these instructions through an interpreter that side-effects and executes these instructions. To do this \nwe need to compose our little interpreters together:\n\n```scala\n// Combine interpreters\nval composedInterpreter = logInterpreter :\u0026: greetingsInterpreters\n```\n\nNow let's run the instructions that were composed together from different instruction sets through the composed interpreter\n```scala\ninstructions.interpret(composedInterpreter)\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalvinlfer%2Ffree-monad-coproduct-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcalvinlfer%2Ffree-monad-coproduct-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalvinlfer%2Ffree-monad-coproduct-example/lists"}