{"id":23902670,"url":"https://github.com/mtumilowicz/scala-cats-implicit-workshop","last_synced_at":"2025-08-16T01:15:45.838Z","repository":{"id":110879088,"uuid":"326057529","full_name":"mtumilowicz/scala-cats-implicit-workshop","owner":"mtumilowicz","description":"Introduction to scala implicit systems with examples.","archived":false,"fork":false,"pushed_at":"2024-11-11T22:49:54.000Z","size":99,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-23T10:44:17.994Z","etag":null,"topics":["cats","cats-core","fp","functional-programing","functional-programming","functional-programming-examples","functional-programming-in-scala","implicits","monad","scala","typeclass-instances","typeclasses","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}},"created_at":"2021-01-01T21:18:16.000Z","updated_at":"2024-11-11T22:49:58.000Z","dependencies_parsed_at":"2023-03-13T13:47:16.534Z","dependency_job_id":null,"html_url":"https://github.com/mtumilowicz/scala-cats-implicit-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mtumilowicz/scala-cats-implicit-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-implicit-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-implicit-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-implicit-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-implicit-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtumilowicz","download_url":"https://codeload.github.com/mtumilowicz/scala-cats-implicit-workshop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-cats-implicit-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270653590,"owners_count":24622793,"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","status":"online","status_checked_at":"2025-08-15T02:00:12.559Z","response_time":110,"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":["cats","cats-core","fp","functional-programing","functional-programming","functional-programming-examples","functional-programming-in-scala","implicits","monad","scala","typeclass-instances","typeclasses","workshop-materials","workshops"],"created_at":"2025-01-04T22:49:45.464Z","updated_at":"2025-08-16T01:15:45.806Z","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-implicit-workshop.svg?branch=master)](https://travis-ci.com/mtumilowicz/scala-cats-implicit-workshop)\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n\n# scala-cats-implicit-workshop\n* references\n    * [Programming Scala, 2nd Edition - Dean Wampler](https://www.oreilly.com/library/view/programming-scala-2nd/9781491950135/)\n    * [Scala with Cats Book - Noel Welsh](https://underscore.io/books/scala-with-cats/)\n    * https://blog.knoldus.com/sorting-in-scala-using-sortedsortby-and-sortwith-function/\n    * https://stackoverflow.com/questions/36432376/implicit-class-vs-implicit-conversion-to-trait\n    * https://typelevel.org/cats/\n    * [Zymposium — Explaining Implicits (Scala 2)](https://www.youtube.com/watch?v=83rm2LxdkAQ)\n    * [Zymposium - ZIO API Design Techniques](https://www.youtube.com/watch?v=48fpPffgnMo)\n    * [Scala Implicits Revisited • Martin Odersky • YOW! 2020](https://www.youtube.com/watch?v=dr0PUXQhg3M)\n    \n## preface\n* goals of this workshop:\n    * introduction to implicits\n    * practicing basic use-cases of implicits\n    * basics of cats  \n* workshop: `workshop` package, answers: `answers` package\n\n## introduction\n* implicits are a powerful, if controversial feature in Scala\n    * they are \"nonlocal\" in the source code\n        * it’s not obvious when an implicit value or method  is being used, which can be \n          confusing to the reader\n* some are imported automatically through `Predef`\n* used to \n    * reduce boilerplate\n    * simulate adding new methods to existing types\n    * support the creation of domain-specific languages (DSLs)\n    \n## arguments\n```\ndef method(arg1: Type1)(implicit context: Type2) = ...\n\ndef anotherMethod() {\n    implicit val defaultContext: Type2 = ...\n    val value = method(...) // implicit value in the local scope will be used as a context\n}\n```\n* only the last arguments can be implicit\n* user does not have to provide argument explicitly\n    * when an implicit argument is omitted, a type-compatible `implicit` value will be used \n      from the enclosing scope, if available\n        * otherwise, a compiler error occurs \n* compiler searches for candidate instances in the implicit scope at the call site:\n    * local or inherited definitions\n    * imported definitions\n    * definitions in the companion object of the type class or the parameter type\n\n## methods\n```\ncase class Data(...)\n\nobject DataOps {\n    implicit def op(implicit data: Data): Type2 = ... // implicit function takes only implicit arguments\n}\n\nclass SomeClass {\n    import DataOps.op // imports implicit method to the scope\n    \n    implicit val data = Data(...) // to be used as implicit value \n    \n    def method(arg1: Type1)(implicit context: Type2) = ...\n    \n    def anotherMethod() {\n        val value = method(...)\n    }\n}\n\n```\n* aren’t chained to get from the available type, through intermediate types, to the target type\n\n## classes\n```\nimplicit class RichInt(n: Int) extends Ordered[Int] {\n   def min(m: Int): Int = if (n \u003c= m) n else m\n   ...\n}\n```\nwill desugar into:\n```\nclass RichInt(n: Int) extends Ordered[Int] {\n  def min(m: Int): Int = if (n \u003c= m) n else m\n  ...\n}\nimplicit final def RichInt(n: Int): RichInt = new RichInt(n)\n```\n* ease the creation of classes which provide extension methods or conversions to another type\n* example\n    ```\n    implicit final class ArrowAssoc[A](val self: A) {\n        def -\u003e [B](y: B): Tuple2[A, B] = Tuple2(self, y)\n    }\n  \n    Map(1 -\u003e 2)\n    ```\n    * when we call `\"a\" -\u003e 1`compiler goes through the following logical steps:\n        1. sees a call `-\u003e` method on `String`\n        2. String has no `-\u003e` method\n           * looks for an implicit conversion in scope to a type that has this method\n        3. finds `ArrowAssoc`\n        4. constructs an `ArrowAssoc` with `\"a\"`\n        5. resolves the `-\u003e 1` part of the expression\n\n## implicitly\n* `Predef` defines a method called implicitly\n    * `def implicitly[A](implicit value: A): A = value`\n* summons any value from implicit scope\n    ```\n    import JsonWriterInstances._\n  \n    val jw = implicitly[JsonWriter[String]]\n    ```\n* syntactic sugar fo defining implicit parameterized argument\n    ```\n    def sortBy[B](f: A =\u003e B)(implicit ord: Ordering[B]): List[A] =\n        list.sortBy(f)(ord)\n    ```\n    idiom is so common that Scala provides a shorthand syntax \n    ```\n    def sortBy[B : Ordering](f: A =\u003e B): List[A] = // 'B : Ordering' is called a context bound\n        list.sortBy(f)(implicitly[Ordering[B]]) // way of obtaining implicit parameter parameter\n    ```\n\n## design mistakes\n1. depending on names\n    * names matter, where they shouldn't\n    * shadowing is a problem\n        ```\n        implicit val a: TC = ...\n        def f(a: A) =\n          ... implicitly[TC] ... // does not work, a is shadowed\n        ```\n1. nesting does not matter\n    ```\n    implicit val a: TC = ...\n    def f(implicit ev: TC) =\n      ... implicitly[TC] ... // does not work, gives an ambiguity: a, ev\n    ```\n    leads to problems with local coherence\n    ```\n    trait Functor[F[_]]\n    trait Monad[F[_]] extends Functor[F]\n    trait Traverse[F[_]] extends Functor[F]\n    def map[A, B, F[_]: Functor](x: F[A], f: A =\u003e B): F[B] = ???\n    def f[A, B, F[_]: Monad: Traverse](x: F[A], f: A =\u003e B): F[B] =\n      map(x, f) // error: ambiguous - should get \"map\" instance from Monad or Traverse?\n    ```\n    adding it implicitly does not work - it brings even more ambiguity\n    ```\n    trait Functor[F[_]]\n    trait Monad[F[_]] extends Functor[F]\n    trait Traverse[F[_]] extends Functor[F]\n    def map[A, B, F[_]: Functor](x: F[A], f: A =\u003e B): F[B] = ???\n    def f[A, B, F[_]: Monad: Traverse](x: F[A], f: A =\u003e B): F[B] =\n      implicit val ev: Functor[F] = implicitly[Monad[F]] // makes it even worse: brings more ambiguity\n      map(x, f) // error: ambiguous - nesting does not matter\n    ```\n1. similar syntax for different concepts\n    * implicit conversions and instances look almost the same but are completely different concepts\n    * conversion: `implicit def a(x: T): U` vs conditional: `implicit def a(implicit x: T): U`\n1. implicit parameters are too close to normal ones\n    * applications of implicit functions are like normal applications\n    * impliciteness is a leaky abstraction\n        ```\n        def f(implicit ev: T): U =\u003e V\n        f(u) // type error\n        f.apply(u) // OK\n        ```\n\n## case study\n* common idioms\n    * boilerplate elimination: providing context information implicitly rather than explicitly\n        * example: `apply[T](body: =\u003e T)(implicit executor: ExecutionContext): Future[T]`\n        * also: transactions, database connections, thread pools, and user sessions\n    * implicit evidence\n        * constrains the allowed types, but doesn’t require them to conform to a common supertype\n        * example\n            ```\n            trait TraversableOnce[+A] ... {\n                ...\n                def toMap[T, U](implicit ev: \u003c:\u003c[A, (T, U)]): immutable.Map[T, U]\n                ...\n            }\n            ```\n            * we only used existence of implicit as confirmation that we operate on a sequence of pairs\n                * no sense in calling `toMap` if the sequence is not a sequence of pairs\n            * `A \u003c:\u003c B` means `A` must be a subtype of `B`\n                * `\u003c:\u003c(A, (T, U))` equivalent to `A \u003c:\u003c (T, U)`\n                * `sealed abstract class \u003c:\u003c[-From, +To] extends (From =\u003e To) with Serializable`\n                    * so we can use evidence as a function\n                        ```\n                        case class Effect[+E, +A](value: Either[E, A]) {\n                            def some[B](implicit ev: A \u003c:\u003c Option[B]): Effect[Option[E], B] =\n                                value.fold(\n                                    e =\u003e Effect.fail(Some(e)),\n                                    a =\u003e ev(a).fold[Effect[Option[E], B]](Effect.fail(Option.empty[E]))(Effect.success))\n                        }\n                      \n                        object Effect {\n                            def fail[E](error: =\u003e E): Effect[E, Nothing] =\n                                Effect(Left(error))\n                          \n                            def success[A](a: A): Effect[Nothing, A] =\n                                Effect(Right(a))\n                        }\n                        ```\n                        and then\n                        ```\n                        val a: Effect[Throwable, Option[Int]] = Effect(Right(Option(1)))\n                        val b: Effect[Option[Throwable], Int] = a.some // a.some(refl)\n                        // turn on show implicit hints (intellij) to see that it is using refl\n                        // implicit def refl[A]: A =:= A = singleton.asInstanceOf[A =:= A]\n                        // A =:= B means A must be exactly B\n                        ```\n                        however\n                        ```\n                        val a: Effect[Throwable, Int] = Effect(Right(1))\n                        val b: Effect[Option[Throwable], Int] = a.some // not compiling: Cannot prove that Int \u003c:\u003c Option[Int]\n                        ```\n                        to customize errors we can code \"alias\" of `\u003c:\u003c`\n                        ```\n                        case class Effect[+E, +A](value: Either[E, A]) {\n                            def some[B](implicit ev: A IsSubtypeOf Option[B]): Effect[Option[E], B] = // modify \u003c:\u003c to IsSubtypeOf\n                        \n                        @implicitNotFound(\"\\nThis operator requires that the output type be a subtype of ${B}\\nBut the actual type was ${A}.\") // we can style compilation error\n                        trait IsSubtypeOf[-A, +B] extends (A =\u003e B)\n                      \n                        object IsSubtypeOf {\n                            implicit def same[A]: IsSubtypeOf[A, A] = (sub: A) =\u003e sub\n                        }\n                        ```\n                        then we can verify that evidence from `IsSubtypeOf` is used\n                        ```\n                        val a: Effect[Throwable, Option[Int]] = Effect(Right[Throwable, Option[Int]](Option(1)))\n                        val b = a.some(same) // after turning on show implicit hints (intellij)\n                        ```\n    * working around limitations due to type erasure\n        ```\n        object M { // compile time error - type erasure\n            def m(seq: Seq[Int]): Unit = ...\n            def m(seq: Seq[String]): Unit = ...\n        }\n        \n        object M { // OK\n            implicit object IntMarker\n            implicit object StringMarker\n        \n            def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit = ...\n        \n            def m(seq: Seq[String])(implicit s: StringMarker.type): Unit = ...\n        }\n        ```\n\n## cats\n* provides abstractions for functional programming in the Scala\n* name is a playful shortening of the word category\n* goal: provide a foundation for an ecosystem of pure, typeful libraries to support functional programming \n  in Scala applications\n\n## type classes\n* is an interface or API that represents some functionality we want to implement\n* in Cats a type class is represented by a trait with at least one type parameter\n    ```\n    trait JsonWriter[A] {\n        def write(value: A): Json\n    }\n    ```\n* instances of a type class provide implementations for the types\n    * concrete implementations + implicit tag\n    ```\n    object JsonWriterInstances {\n        implicit val stringWriter: JsonWriter[String] = (value: String) =\u003e JsString(value) // single abstract method\n        implicit val personWriter: JsonWriter[Person] = // creating anonymous class explicitly\n            new JsonWriter[Person] {\n                def write(value: Person): Json =\n                    JsObject(Map(\n                        \"name\" -\u003e JsString(value.name),\n                        \"email\" -\u003e JsString(value.email)\n                    ))\n        }\n        // etc...\n    }\n    ```\n    * two common ways of specifying an interface\n        * interface objects\n            ```\n            object Json {\n                def toJson[A](value: A)(implicit w: JsonWriter[A]): Json =\n                    w.write(value)\n            }\n            ```\n            and then\n            ```\n            import JsonWriterInstances._ // import any type class instances we care about\n            \n            Json.toJson(Person(\"Dave\", \"dave@example.com\")) // Json.toJson(Person(\"Dave\", \"dave@example.com\"))(personWriter)\n            ```\n        * interface syntax\n            ```\n            object JsonSyntax {\n                implicit class JsonWriterOps[A](value: A) { // extension methods to extend existing types with interface methods\n                    def toJson(implicit w: JsonWriter[A]): Json =\n                        w.write(value)\n                }\n            }\n            ```\n            and then\n            ```\n            import JsonWriterInstances._\n            import JsonSyntax._\n          \n            Person(\"Dave\", \"dave@example.com\").toJson // Person(\"Dave\", \"dave@example.com\").toJson(personWriter)\n            ```\n\n## recursive resolution\n* consider `JsonWriter[Option[A]]`\n* we need instance for every `A`\n    ```\n    implicit val optionIntWriter: JsonWriter[Option[Int]] = ???\n    implicit val optionPersonWriter: JsonWriter[Option[Person]] = ???\n    // and so on...\n    ```\n    * it doesn't scale\n* we can abstract the code for handling `Option[A]` into a common constructor based on the instance for `A`\n    ```\n    implicit def optionWriter[A](implicit writer: JsonWriter[A]): JsonWriter[Option[A]] = \n        option: Option[A] =\u003e option match {\n            case Some(aValue) =\u003e writer.write(aValue)\n            case None =\u003e JsNull\n        }\n    ```\n    then\n    ```\n    Json.toJson(Option(\"A string\")) // Json.toJson(Option(\"A string\"))(optionWriter(stringWriter))\n    ```\n\n## performance\n* compile-time overhead: project is slow to build\n* runtime overhead: due to the extra layers of indirection from the wrapper types\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-cats-implicit-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtumilowicz%2Fscala-cats-implicit-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-cats-implicit-workshop/lists"}