From 3cae48c4c54c7f802eb6d62100d01e86d4cebbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 13:38:36 -0600 Subject: [PATCH 01/33] =?UTF-8?q?fix=20typo:=20Nexted=20=E2=86=92=20Nested?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/adt/scala.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/adt/scala.md b/src/pages/adt/scala.md index 95b1593c..3193a124 100644 --- a/src/pages/adt/scala.md +++ b/src/pages/adt/scala.md @@ -43,7 +43,7 @@ enum A { } ``` -In other words you can't write `final case class` inside an `enum`. You also can't nest `enum` inside `enum`. Nexted logical ors can be rewritten into a single logical or containing only logical ands (known as disjunctive normal form) so this is not a limitation in practice. However the Scala 2 representation is still available in Scala 3 should you want more expressivity. +In other words you can't write `final case class` inside an `enum`. You also can't nest `enum` inside `enum`. Nested logical ors can be rewritten into a single logical or containing only logical ands (known as disjunctive normal form) so this is not a limitation in practice. However the Scala 2 representation is still available in Scala 3 should you want more expressivity. ### Algebraic Data Types in Scala 2 From 24aedd310e58d22075dcac0f7775d8dc1be9d1d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 09:42:28 -0600 Subject: [PATCH 02/33] =?UTF-8?q?sbt:=201.6.1=20=E2=86=92=201.8.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 3161d214..46e43a97 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.1 +sbt.version=1.8.2 From ba0a40f8afd9519b35eba9aa7b87b5e1084a6a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 09:43:57 -0600 Subject: [PATCH 03/33] =?UTF-8?q?scala:=202.13.8=20=E2=86=92=203.2.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 60c4b770..4518ee9a 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ ThisBuild / name := "scala-with-cats" ThisBuild / organization := "com.scalawithcats" ThisBuild / version := "0.0.1" -ThisBuild / scalaVersion := "2.13.8" +ThisBuild / scalaVersion := "3.2.2" ThisBuild / useSuperShell := false Global / logLevel := Level.Warn From 39aeddfcf7b7f0c2ed8303703fe2ca7241c042d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 09:44:18 -0600 Subject: [PATCH 04/33] =?UTF-8?q?cats:=202.7.0=20=E2=86=92=202.9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 4518ee9a..857d1b52 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ enablePlugins(MdocPlugin) mdocIn := sourceDirectory.value / "pages" mdocOut := target.value / "pages" -val catsVersion = "2.7.0" +val catsVersion = "2.9.0" libraryDependencies ++= Seq("org.typelevel" %% "cats-core" % catsVersion) From f5916ec1d24d0ada4e6de127e13ce3f03b5605be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 09:50:04 -0600 Subject: [PATCH 05/33] Substitute Scala 2 Typelevel's Kind Projector Plugin with Scala 3 built-in Kind Projector feature --- build.sbt | 7 +++++-- src/pages/links.md | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 857d1b52..57cb8c5f 100644 --- a/build.sbt +++ b/build.sbt @@ -14,12 +14,15 @@ enablePlugins(MdocPlugin) mdocIn := sourceDirectory.value / "pages" mdocOut := target.value / "pages" +scalacOptions ++= Seq( + "-explain", // Better diagnostics + "-Ykind-projector:underscores" // In-lieu of kind-projector +) + val catsVersion = "2.9.0" libraryDependencies ++= Seq("org.typelevel" %% "cats-core" % catsVersion) -addCompilerPlugin("org.typelevel" % "kind-projector" % "0.13.2" cross CrossVersion.full) - mdocVariables := Map( "SCALA_VERSION" -> scalaVersion.value, "CATS_VERSION" -> catsVersion diff --git a/src/pages/links.md b/src/pages/links.md index 48909519..7a63fe53 100644 --- a/src/pages/links.md +++ b/src/pages/links.md @@ -113,6 +113,7 @@ [link-iterator-pattern]: https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf [link-json]: http://json.org/ [link-kind-projector]: https://github.com/typelevel/kind-projector +[link-kind-projector-migration]: https://docs.scala-lang.org/scala3/guides/migration/plugin-kind-projector.html [link-map-reduce-monoid]: http://arxiv.org/abs/1304.7544 [link-map-reduce]: http://research.google.com/archive/map-reduce.html [link-mdoc]: https://github.com/scalameta/mdoc From 1b79221824f09af553062db7710665b06e968693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 09:59:28 -0600 Subject: [PATCH 06/33] =?UTF-8?q?sbt-mdoc:=202.3.0=20=E2=86=92=202.3.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7e3fed96..b1563996 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.0" ) +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7" ) // addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.10") From 17006e33d3332654dec4065091fbb6fd1d7187de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 11:20:31 -0600 Subject: [PATCH 07/33] Upgrade overhaul: fix errors/warnings after upgrades --- src/pages/case-studies/crdt/abstraction.md | 2 +- src/pages/case-studies/testing/index.md | 2 +- src/pages/case-studies/validation/kleisli.md | 2 +- src/pages/case-studies/validation/map.md | 2 +- src/pages/foldable-traverse/traverse-cats.md | 4 ++-- src/pages/monads/custom-instances.md | 25 ++++++++++---------- src/pages/preface/versions.md | 3 ++- src/pages/type-classes/printable.md | 6 ++--- 8 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/pages/case-studies/crdt/abstraction.md b/src/pages/case-studies/crdt/abstraction.md index 52d214db..d7a1154a 100644 --- a/src/pages/case-studies/crdt/abstraction.md +++ b/src/pages/case-studies/crdt/abstraction.md @@ -206,7 +206,7 @@ using an `implicit def`: ```scala mdoc:silent implicit def gcounterInstance[F[_,_], K, V] - (implicit kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]) = + (implicit kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]): GCounter[F, K, V] = new GCounter[F, K, V] { def increment(f: F[K, V])(key: K, value: V) (implicit m: CommutativeMonoid[V]): F[K, V] = { diff --git a/src/pages/case-studies/testing/index.md b/src/pages/case-studies/testing/index.md index f1d3ba76..17257b27 100644 --- a/src/pages/case-studies/testing/index.md +++ b/src/pages/case-studies/testing/index.md @@ -53,7 +53,7 @@ We want to test its ability to sum values, regardless of where it is getting them from. Here's an example: -```scala mdoc:warn +```scala mdoc:fail def testTotalUptime() = { val hosts = Map("host1" -> 10, "host2" -> 6) val client = new TestUptimeClient(hosts) diff --git a/src/pages/case-studies/validation/kleisli.md b/src/pages/case-studies/validation/kleisli.md index 91d0421e..dfa6955a 100644 --- a/src/pages/case-studies/validation/kleisli.md +++ b/src/pages/case-studies/validation/kleisli.md @@ -330,7 +330,7 @@ def createUser( email: String): Either[Errors, User] = ( checkUsername.run(username), checkEmail.run(email) -).mapN(User) +).mapN(User.apply) ``` ```scala mdoc diff --git a/src/pages/case-studies/validation/map.md b/src/pages/case-studies/validation/map.md index 88c4a703..130a7f2b 100644 --- a/src/pages/case-studies/validation/map.md +++ b/src/pages/case-studies/validation/map.md @@ -647,7 +647,7 @@ final case class User(username: String, email: String) def createUser( username: String, email: String): Validated[Errors, User] = - (checkUsername(username), checkEmail(email)).mapN(User) + (checkUsername(username), checkEmail(email)).mapN(User.apply) ``` We can check our work by creating diff --git a/src/pages/foldable-traverse/traverse-cats.md b/src/pages/foldable-traverse/traverse-cats.md index b4a78c72..8a38ae5d 100644 --- a/src/pages/foldable-traverse/traverse-cats.md +++ b/src/pages/foldable-traverse/traverse-cats.md @@ -74,8 +74,8 @@ import cats.syntax.traverse._ // for sequence and traverse ``` ```scala mdoc -Await.result(hostnames.traverse(getUptime), 1.second) -Await.result(numbers.sequence, 1.second) +Await.result(hostnames.traverse[Future, Int](getUptime), 1.second) +Await.result(numbers.sequence[Future, Int], 1.second) ``` As you can see, this is much more compact and readable diff --git a/src/pages/monads/custom-instances.md b/src/pages/monads/custom-instances.md index e1c005ce..d9854cc0 100644 --- a/src/pages/monads/custom-instances.md +++ b/src/pages/monads/custom-instances.md @@ -167,7 +167,7 @@ the non-tail-recursive solution falls out: ```scala mdoc:silent import cats.Monad -implicit val treeMonad = new Monad[Tree] { +implicit val treeMonad: Monad[Tree] = new Monad[Tree] { def pure[A](value: A): Tree[A] = Leaf(value) @@ -180,14 +180,14 @@ implicit val treeMonad = new Monad[Tree] { func(value) } - def tailRecM[A, B](a: A) - (func: A => Tree[Either[A, B]]): Tree[B] = - flatMap(func(a)) { - case Left(value) => - tailRecM(value)(func) - case Right(value) => - Leaf(value) - } + def tailRecM[A, B](a: A) + (func: A => Tree[Either[A, B]]): Tree[B] = + flatMap(func(a)) { + case Left(value) => + tailRecM(value)(func) + case Right(value) => + Leaf(value) + } } ``` @@ -219,7 +219,7 @@ def leaf[A](value: A): Tree[A] = import cats.Monad import scala.annotation.tailrec -implicit val treeMonad = new Monad[Tree] { +implicit val treeMonad: Monad[Tree] = new Monad[Tree] { def pure[A](value: A): Tree[A] = Leaf(value) @@ -232,8 +232,9 @@ implicit val treeMonad = new Monad[Tree] { func(value) } - def tailRecM[A, B](arg: A) - (func: A => Tree[Either[A, B]]): Tree[B] = { + def tailRecM[A, B](arg: A)( + func: A => Tree[Either[A, B]] + ): Tree[B] = { @tailrec def loop( open: List[Tree[Either[A, B]]], diff --git a/src/pages/preface/versions.md b/src/pages/preface/versions.md index 9da7f8e5..4ebecc72 100644 --- a/src/pages/preface/versions.md +++ b/src/pages/preface/versions.md @@ -11,7 +11,8 @@ libraryDependencies += "org.typelevel" %% "cats-core" % "@CATS_VERSION@" scalacOptions ++= Seq( - "-Xfatal-warnings" + "-explain", + "-Werror" ) ``` diff --git a/src/pages/type-classes/printable.md b/src/pages/type-classes/printable.md index 8153fb17..609fe4c9 100644 --- a/src/pages/type-classes/printable.md +++ b/src/pages/type-classes/printable.md @@ -39,11 +39,11 @@ and package them in `PrintableInstances`: ```scala mdoc:silent object PrintableInstances { - implicit val stringPrintable = new Printable[String] { + implicit val stringPrintable: Printable[String] = new Printable[String] { def format(input: String) = input } - implicit val intPrintable = new Printable[Int] { + implicit val intPrintable: Printable[Int] = new Printable[Int] { def format(input: Int) = input.toString } } @@ -106,7 +106,7 @@ or a separate object to act as a namespace: ```scala mdoc:silent import PrintableInstances._ -implicit val catPrintable = new Printable[Cat] { +implicit val catPrintable: Printable[Cat] = new Printable[Cat] { def format(cat: Cat) = { val name = Printable.format(cat.name) val age = Printable.format(cat.age) From 4d1c8f86b1c259b0e51a5ba2b31e6a496beb02c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 11:25:32 -0600 Subject: [PATCH 08/33] Fix Pandoc Deprecation of --self-contained; use --embed-resources --standalone --- project/Pandoc.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Pandoc.scala b/project/Pandoc.scala index c5ad2229..1c940481 100644 --- a/project/Pandoc.scala +++ b/project/Pandoc.scala @@ -118,7 +118,7 @@ object Pandoc { "--table-of-contents", "--highlight-style tango", "--standalone", - "--self-contained", + "--embed-resources", ), extras, metadata, From 5871a966fef8ed9de7b6cf9ea036c083218f104a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 12:03:44 -0600 Subject: [PATCH 09/33] =?UTF-8?q?implicit=20val/implicit=20param=20?= =?UTF-8?q?=E2=86=92=20given/using?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/applicatives/semigroupal.md | 2 +- src/pages/case-studies/crdt/abstraction.md | 64 +++++++-------- src/pages/case-studies/crdt/generalisation.md | 18 ++--- src/pages/case-studies/testing/index.md | 2 +- src/pages/case-studies/validation/check.md | 10 +-- src/pages/case-studies/validation/kleisli.md | 6 +- src/pages/case-studies/validation/map.md | 32 ++++---- src/pages/foldable-traverse/foldable.md | 4 +- src/pages/functors/cats.md | 32 ++++---- .../functors/contravariant-invariant-cats.md | 2 +- src/pages/functors/contravariant-invariant.md | 79 ++++++++----------- src/pages/monads/custom-instances.md | 6 +- src/pages/monads/monad-error.md | 6 +- src/pages/monoids/cats.md | 5 +- src/pages/monoids/index.md | 42 ++++------ src/pages/monoids/summary.md | 2 +- src/pages/type-classes/anatomy.md | 40 +++++----- src/pages/type-classes/cats.md | 12 ++- src/pages/type-classes/equal.md | 4 +- src/pages/type-classes/implicits.md | 54 ++++++------- src/pages/type-classes/printable.md | 21 +++-- 21 files changed, 198 insertions(+), 245 deletions(-) diff --git a/src/pages/applicatives/semigroupal.md b/src/pages/applicatives/semigroupal.md index 09bba7d1..daca552a 100644 --- a/src/pages/applicatives/semigroupal.md +++ b/src/pages/applicatives/semigroupal.md @@ -185,7 +185,7 @@ val tupleToCat: (String, Int, List[String]) => Cat = val catToTuple: Cat => (String, Int, List[String]) = cat => (cat.name, cat.yearOfBirth, cat.favoriteFoods) -implicit val catMonoid: Monoid[Cat] = ( +given catMonoid: Monoid[Cat] = ( Monoid[String], Monoid[Int], Monoid[List[String]] diff --git a/src/pages/case-studies/crdt/abstraction.md b/src/pages/case-studies/crdt/abstraction.md index d7a1154a..6e3d32b2 100644 --- a/src/pages/case-studies/crdt/abstraction.md +++ b/src/pages/case-studies/crdt/abstraction.md @@ -33,14 +33,12 @@ trait BoundedSemiLattice[A] extends CommutativeMonoid[A] { } object BoundedSemiLattice { - implicit val intInstance: BoundedSemiLattice[Int] = - new BoundedSemiLattice[Int] { - def combine(a1: Int, a2: Int): Int = - a1 max a2 + given intInstance: BoundedSemiLattice[Int] with + def combine(a1: Int, a2: Int): Int = + a1 max a2 - val empty: Int = - 0 - } + val empty: Int = + 0 implicit def setInstance[A]: BoundedSemiLattice[Set[A]] = new BoundedSemiLattice[Set[A]]{ @@ -55,18 +53,18 @@ object BoundedSemiLattice { ```scala mdoc:silent trait GCounter[F[_,_],K, V] { def increment(f: F[K, V])(k: K, v: V) - (implicit m: CommutativeMonoid[V]): F[K, V] + (using m: CommutativeMonoid[V]): F[K, V] def merge(f1: F[K, V], f2: F[K, V]) - (implicit b: BoundedSemiLattice[V]): F[K, V] + (using b: BoundedSemiLattice[V]): F[K, V] def total(f: F[K, V]) - (implicit m: CommutativeMonoid[V]): V + (using m: CommutativeMonoid[V]): V } object GCounter { def apply[F[_,_], K, V] - (implicit counter: GCounter[F, K, V]) = + (using counter: GCounter[F, K, V]) = counter } ``` @@ -91,17 +89,17 @@ import cats.syntax.foldable._ // for combineAll implicit def mapGCounterInstance[K, V]: GCounter[Map, K, V] = new GCounter[Map, K, V] { def increment(map: Map[K, V])(key: K, value: V) - (implicit m: CommutativeMonoid[V]): Map[K, V] = { + (using m: CommutativeMonoid[V]): Map[K, V] = { val total = map.getOrElse(key, m.empty) |+| value map + (key -> total) } def merge(map1: Map[K, V], map2: Map[K, V]) - (implicit b: BoundedSemiLattice[V]): Map[K, V] = + (using b: BoundedSemiLattice[V]): Map[K, V] = map1 |+| map2 def total(map: Map[K, V]) - (implicit m: CommutativeMonoid[V]): V = + (using m: CommutativeMonoid[V]): V = map.values.toList.combineAll } ``` @@ -160,21 +158,19 @@ the companion object for `KeyValueStore` to place it in global implicit scope: ```scala mdoc:silent -implicit val mapKeyValueStoreInstance: KeyValueStore[Map] = - new KeyValueStore[Map] { - def put[K, V](f: Map[K, V])(k: K, v: V): Map[K, V] = - f + (k -> v) +given mapKeyValueStoreInstance: KeyValueStore[Map] with + def put[K, V](f: Map[K, V])(k: K, v: V): Map[K, V] = + f + (k -> v) - def get[K, V](f: Map[K, V])(k: K): Option[V] = - f.get(k) + def get[K, V](f: Map[K, V])(k: K): Option[V] = + f.get(k) - override def getOrElse[K, V](f: Map[K, V]) - (k: K, default: V): V = - f.getOrElse(k, default) + override def getOrElse[K, V](f: Map[K, V]) + (k: K, default: V): V = + f.getOrElse(k, default) - def values[K, V](f: Map[K, V]): List[V] = - f.values.toList - } + def values[K, V](f: Map[K, V]): List[V] = + f.values.toList ``` @@ -184,17 +180,17 @@ to enhance data types for which we have instances: ```scala mdoc:silent implicit class KvsOps[F[_,_], K, V](f: F[K, V]) { def put(key: K, value: V) - (implicit kvs: KeyValueStore[F]): F[K, V] = + (using kvs: KeyValueStore[F]): F[K, V] = kvs.put(f)(key, value) - def get(key: K)(implicit kvs: KeyValueStore[F]): Option[V] = + def get(key: K)(using kvs: KeyValueStore[F]): Option[V] = kvs.get(f)(key) def getOrElse(key: K, default: V) - (implicit kvs: KeyValueStore[F]): V = + (using kvs: KeyValueStore[F]): V = kvs.getOrElse(f)(key, default) - def values(implicit kvs: KeyValueStore[F]): List[V] = + def values(using kvs: KeyValueStore[F]): List[V] = kvs.values(f) } ``` @@ -206,19 +202,19 @@ using an `implicit def`: ```scala mdoc:silent implicit def gcounterInstance[F[_,_], K, V] - (implicit kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]): GCounter[F, K, V] = + (using kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]): GCounter[F, K, V] = new GCounter[F, K, V] { def increment(f: F[K, V])(key: K, value: V) - (implicit m: CommutativeMonoid[V]): F[K, V] = { + (using m: CommutativeMonoid[V]): F[K, V] = { val total = f.getOrElse(key, m.empty) |+| value f.put(key, total) } def merge(f1: F[K, V], f2: F[K, V]) - (implicit b: BoundedSemiLattice[V]): F[K, V] = + (using b: BoundedSemiLattice[V]): F[K, V] = f1 |+| f2 - def total(f: F[K, V])(implicit m: CommutativeMonoid[V]): V = + def total(f: F[K, V])(using m: CommutativeMonoid[V]): V = f.values.combineAll } ``` diff --git a/src/pages/case-studies/crdt/generalisation.md b/src/pages/case-studies/crdt/generalisation.md index 1b4fb4d8..5b9f9e0a 100644 --- a/src/pages/case-studies/crdt/generalisation.md +++ b/src/pages/case-studies/crdt/generalisation.md @@ -153,14 +153,12 @@ object wrapper { } object BoundedSemiLattice { - implicit val intInstance: BoundedSemiLattice[Int] = - new BoundedSemiLattice[Int] { - def combine(a1: Int, a2: Int): Int = - a1 max a2 + given intInstance: BoundedSemiLattice[Int] with + def combine(a1: Int, a2: Int): Int = + a1 max a2 - val empty: Int = - 0 - } + val empty: Int = + 0 implicit def setInstance[A]: BoundedSemiLattice[Set[A]] = new BoundedSemiLattice[Set[A]]{ @@ -203,16 +201,16 @@ import cats.syntax.foldable._ // for combineAll final case class GCounter[A](counters: Map[String,A]) { def increment(machine: String, amount: A) - (implicit m: CommutativeMonoid[A]): GCounter[A] = { + (using m: CommutativeMonoid[A]): GCounter[A] = { val value = amount |+| counters.getOrElse(machine, m.empty) GCounter(counters + (machine -> value)) } def merge(that: GCounter[A]) - (implicit b: BoundedSemiLattice[A]): GCounter[A] = + (using b: BoundedSemiLattice[A]): GCounter[A] = GCounter(this.counters |+| that.counters) - def total(implicit m: CommutativeMonoid[A]): A = + def total(using m: CommutativeMonoid[A]): A = this.counters.values.toList.combineAll } ``` diff --git a/src/pages/case-studies/testing/index.md b/src/pages/case-studies/testing/index.md index 17257b27..76b34648 100644 --- a/src/pages/case-studies/testing/index.md +++ b/src/pages/case-studies/testing/index.md @@ -280,7 +280,7 @@ import cats.Applicative import cats.syntax.functor._ // for map class UptimeService[F[_]](client: UptimeClient[F]) - (implicit a: Applicative[F]) { + (using a: Applicative[F]) { def getTotalUptime(hostnames: List[String]): F[Int] = hostnames.traverse(client.getUptime).map(_.sum) diff --git a/src/pages/case-studies/validation/check.md b/src/pages/case-studies/validation/check.md index a9d89c3a..c6b64ad6 100644 --- a/src/pages/case-studies/validation/check.md +++ b/src/pages/case-studies/validation/check.md @@ -141,7 +141,7 @@ final case class CheckF[E, A](func: A => Either[E, A]) { func(a) def and(that: CheckF[E, A]) - (implicit s: Semigroup[E]): CheckF[E, A] = + (using s: Semigroup[E]): CheckF[E, A] = CheckF { a => (this(a), that(a)) match { case (Left(e1), Left(e2)) => (e1 |+| e2).asLeft @@ -200,7 +200,7 @@ final case class CheckF[E, A](func: A => Either[E, A]) { func(a) def and(that: CheckF[E, A]) - (implicit s: Semigroup[E]): CheckF[E, A] = + (using s: Semigroup[E]): CheckF[E, A] = CheckF { a => (this(a), that(a)) match { case (Left(e1), Left(e2)) => (e1 |+| e2).asLeft @@ -246,7 +246,7 @@ sealed trait Check[E, A] { def and(that: Check[E, A]): Check[E, A] = And(this, that) - def apply(a: A)(implicit s: Semigroup[E]): Either[E, A] = + def apply(a: A)(using s: Semigroup[E]): Either[E, A] = this match { case Pure(func) => func(a) @@ -342,7 +342,7 @@ sealed trait Check[E, A] { def and(that: Check[E, A]): Check[E, A] = And(this, that) - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = this match { case Pure(func) => func(a) @@ -390,7 +390,7 @@ sealed trait Check[E, A] { def or(that: Check[E, A]): Check[E, A] = Or(this, that) - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = this match { case Pure(func) => func(a) diff --git a/src/pages/case-studies/validation/kleisli.md b/src/pages/case-studies/validation/kleisli.md index dfa6955a..84618d9d 100644 --- a/src/pages/case-studies/validation/kleisli.md +++ b/src/pages/case-studies/validation/kleisli.md @@ -126,7 +126,7 @@ import cats.Semigroup import cats.data.Validated sealed trait Predicate[E, A] { - def run(implicit s: Semigroup[E]): A => Either[E, A] = + def run(using s: Semigroup[E]): A => Either[E, A] = (a: A) => this(a).toEither def apply(a: A): Validated[E, A] = @@ -186,10 +186,10 @@ sealed trait Predicate[E, A] { def or(that: Predicate[E, A]): Predicate[E, A] = Or(this, that) - def run(implicit s: Semigroup[E]): A => Either[E, A] = + def run(using s: Semigroup[E]): A => Either[E, A] = (a: A) => this(a).toEither - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = this match { case Pure(func) => func(a) diff --git a/src/pages/case-studies/validation/map.md b/src/pages/case-studies/validation/map.md index 130a7f2b..47a70d50 100644 --- a/src/pages/case-studies/validation/map.md +++ b/src/pages/case-studies/validation/map.md @@ -102,7 +102,7 @@ sealed trait Predicate[E, A] { def or(that: Predicate[E, A]): Predicate[E, A] = Or(this, that) - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = this match { case Pure(func) => func(a) @@ -170,7 +170,7 @@ sealed trait Predicate[E, A] { def or(that: Predicate[E, A]): Predicate[E, A] = Or(this, that) - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = this match { case Pure(func) => func(a) @@ -211,7 +211,7 @@ import cats.data.Validated sealed trait Check[E, A, B] { import Check._ - def apply(in: A)(implicit s: Semigroup[E]): Validated[E, B] + def apply(in: A)(using s: Semigroup[E]): Validated[E, B] def map[C](f: B => C): Check[E, A, C] = Map[E, A, B, C](this, f) @@ -222,14 +222,14 @@ object Check { check: Check[E, A, B], func: B => C) extends Check[E, A, C] { - def apply(in: A)(implicit s: Semigroup[E]): Validated[E, C] = + def apply(in: A)(using s: Semigroup[E]): Validated[E, C] = check(in).map(func) } final case class Pure[E, A]( pred: Predicate[E, A]) extends Check[E, A, A] { - def apply(in: A)(implicit s: Semigroup[E]): Validated[E, A] = + def apply(in: A)(using s: Semigroup[E]): Validated[E, A] = pred(in) } @@ -289,7 +289,7 @@ import cats.data.Validated ```scala mdoc:silent sealed trait Check[E, A, B] { - def apply(in: A)(implicit s: Semigroup[E]): Validated[E, B] + def apply(in: A)(using s: Semigroup[E]): Validated[E, B] def flatMap[C](f: B => Check[E, A, C]) = FlatMap[E, A, B, C](this, f) @@ -301,7 +301,7 @@ final case class FlatMap[E, A, B, C]( check: Check[E, A, B], func: B => Check[E, A, C]) extends Check[E, A, C] { - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, C] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, C] = check(a).withEither(_.flatMap(b => func(b)(a).toEither)) } @@ -342,7 +342,7 @@ import cats.data.Validated ``` ```scala mdoc:silent sealed trait Check[E, A, B] { - def apply(in: A)(implicit s: Semigroup[E]): Validated[E, B] + def apply(in: A)(using s: Semigroup[E]): Validated[E, B] def andThen[C](that: Check[E, B, C]): Check[E, A, C] = AndThen[E, A, B, C](this, that) @@ -352,7 +352,7 @@ final case class AndThen[E, A, B, C]( check1: Check[E, A, B], check2: Check[E, B, C]) extends Check[E, A, C] { - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, C] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, C] = check1(a).withEither(_.flatMap(b => check2(b).toEither)) } ``` @@ -395,7 +395,7 @@ sealed trait Predicate[E, A] { def or(that: Predicate[E, A]): Predicate[E, A] = Or(this, that) - def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] = + def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = this match { case Pure(func) => func(a) @@ -451,7 +451,7 @@ import cats.syntax.validated._ // for valid and invalid sealed trait Check[E, A, B] { import Check._ - def apply(in: A)(implicit s: Semigroup[E]): Validated[E, B] + def apply(in: A)(using s: Semigroup[E]): Validated[E, B] def map[C](f: B => C): Check[E, A, C] = Map[E, A, B, C](this, f) @@ -469,7 +469,7 @@ object Check { func: B => C) extends Check[E, A, C] { def apply(a: A) - (implicit s: Semigroup[E]): Validated[E, C] = + (using s: Semigroup[E]): Validated[E, C] = check(a) map func } @@ -478,7 +478,7 @@ object Check { func: B => Check[E, A, C]) extends Check[E, A, C] { def apply(a: A) - (implicit s: Semigroup[E]): Validated[E, C] = + (using s: Semigroup[E]): Validated[E, C] = check(a).withEither(_.flatMap(b => func(b)(a).toEither)) } @@ -487,7 +487,7 @@ object Check { next: Check[E, B, C]) extends Check[E, A, C] { def apply(a: A) - (implicit s: Semigroup[E]): Validated[E, C] = + (using s: Semigroup[E]): Validated[E, C] = check(a).withEither(_.flatMap(b => next(b).toEither)) } @@ -495,7 +495,7 @@ object Check { func: A => Validated[E, B]) extends Check[E, A, B] { def apply(a: A) - (implicit s: Semigroup[E]): Validated[E, B] = + (using s: Semigroup[E]): Validated[E, B] = func(a) } @@ -503,7 +503,7 @@ object Check { pred: Predicate[E, A]) extends Check[E, A, A] { def apply(a: A) - (implicit s: Semigroup[E]): Validated[E, A] = + (using s: Semigroup[E]): Validated[E, A] = pred(a) } diff --git a/src/pages/foldable-traverse/foldable.md b/src/pages/foldable-traverse/foldable.md index 576aef44..79776895 100644 --- a/src/pages/foldable-traverse/foldable.md +++ b/src/pages/foldable-traverse/foldable.md @@ -141,7 +141,7 @@ one using `scala.math.Numeric` import scala.math.Numeric def sumWithNumeric[A](list: List[A]) - (implicit numeric: Numeric[A]): A = + (using numeric: Numeric[A]): A = list.foldRight(numeric.zero)(numeric.plus) ``` @@ -156,7 +156,7 @@ and one using `cats.Monoid` import cats.Monoid def sumWithMonoid[A](list: List[A]) - (implicit monoid: Monoid[A]): A = + (using monoid: Monoid[A]): A = list.foldRight(monoid.empty)(monoid.combine) import cats.instances.int._ // for Monoid diff --git a/src/pages/functors/cats.md b/src/pages/functors/cats.md index 3ae4835c..24f0d461 100644 --- a/src/pages/functors/cats.md +++ b/src/pages/functors/cats.md @@ -83,7 +83,7 @@ no matter what functor context it's in: ```scala mdoc:silent def doMath[F[_]](start: F[Int]) - (implicit functor: Functor[F]): F[Int] = + (using functor: Functor[F]): F[Int] = start.map(n => n + 1 * 2) import cats.instances.option._ // for Functor @@ -103,7 +103,7 @@ Here's a simplified version of the code: ```scala implicit class FunctorOps[F[_], A](src: F[A]) { def map[B](func: A => B) - (implicit functor: Functor[F]): F[B] = + (using functor: Functor[F]): F[B] = functor.map(src)(func) } ``` @@ -153,11 +153,9 @@ even though such a thing already exists in [`cats.instances`][cats.instances]. The implementation is trivial---we simply call `Option's` `map` method: ```scala mdoc:silent -implicit val optionFunctor: Functor[Option] = - new Functor[Option] { - def map[A, B](value: Option[A])(func: A => B): Option[B] = - value.map(func) - } +given optionFunctor: Functor[Option] with + def map[A, B](value: Option[A])(func: A => B): Option[B] = + value.map(func) ``` Sometimes we need to inject dependencies into our instances. @@ -171,7 +169,7 @@ so we have to account for the dependency when we create the instance: import scala.concurrent.{Future, ExecutionContext} implicit def futureFunctor - (implicit ec: ExecutionContext): Functor[Future] = + (using ec: ExecutionContext): Functor[Future] = new Functor[Future] { def map[A, B](value: Future[A])(func: A => B): Future[B] = value.map(func) @@ -219,16 +217,14 @@ with the same pattern of `Branch` and `Leaf` nodes: ```scala mdoc:silent import cats.Functor -implicit val treeFunctor: Functor[Tree] = - new Functor[Tree] { - def map[A, B](tree: Tree[A])(func: A => B): Tree[B] = - tree match { - case Branch(left, right) => - Branch(map(left)(func), map(right)(func)) - case Leaf(value) => - Leaf(func(value)) - } - } +given treeFunctor: Functor[Tree] with + def map[A, B](tree: Tree[A])(func: A => B): Tree[B] = + tree match { + case Branch(left, right) => + Branch(map(left)(func), map(right)(func)) + case Leaf(value) => + Leaf(func(value)) + } ``` Let's use our `Functor` to transform some `Trees`: diff --git a/src/pages/functors/contravariant-invariant-cats.md b/src/pages/functors/contravariant-invariant-cats.md index 71341612..f6742d42 100644 --- a/src/pages/functors/contravariant-invariant-cats.md +++ b/src/pages/functors/contravariant-invariant-cats.md @@ -99,7 +99,7 @@ import cats.instances.string._ // for Monoid import cats.syntax.invariant._ // for imap import cats.syntax.semigroup._ // for |+| -implicit val symbolMonoid: Monoid[Symbol] = +given symbolMonoid: Monoid[Symbol] = Monoid[String].imap(Symbol.apply)(_.name) ``` diff --git a/src/pages/functors/contravariant-invariant.md b/src/pages/functors/contravariant-invariant.md index 30b07dd0..f2f60ca1 100644 --- a/src/pages/functors/contravariant-invariant.md +++ b/src/pages/functors/contravariant-invariant.md @@ -55,7 +55,7 @@ trait Printable[A] { ??? } -def format[A](value: A)(implicit p: Printable[A]): String = +def format[A](value: A)(using p: Printable[A]): String = p.format(value) ``` @@ -103,7 +103,7 @@ trait Printable[A] { self => } } -def format[A](value: A)(implicit p: Printable[A]): String = +def format[A](value: A)(using p: Printable[A]): String = p.format(value) ``` @@ -113,17 +113,13 @@ let's define some instances of `Printable` for `String` and `Boolean`: ```scala mdoc:silent -implicit val stringPrintable: Printable[String] = - new Printable[String] { - def format(value: String): String = - s"'${value}'" - } +given stringPrintable: Printable[String] with + def format(value: String): String = + s"'${value}'" -implicit val booleanPrintable: Printable[Boolean] = - new Printable[Boolean] { - def format(value: Boolean): String = - if(value) "yes" else "no" - } +given booleanPrintable: Printable[Boolean] with + def format(value: Boolean): String = + if(value) "yes" else "no" ``` ```scala mdoc @@ -147,7 +143,7 @@ create your instance from an existing instance using `contramap`. ```scala mdoc:invisible -implicit def boxPrintable[A](implicit p: Printable[A]): Printable[Box[A]] = +implicit def boxPrintable[A](using p: Printable[A]): Printable[Box[A]] = p.contramap[Box[A]](_.value) ``` @@ -210,24 +206,21 @@ trait Printable[A] { } } -def format[A](value: A)(implicit p: Printable[A]): String = +def format[A](value: A)(using p: Printable[A]): String = p.format(value) -implicit val stringPrintable: Printable[String] = - new Printable[String] { - def format(value: String): String = - s"'${value}'" - } +given stringPrintable: Printable[String] with + def format(value: String): String = + s"'${value}'" + +given booleanPrintable: Printable[Boolean] with + def format(value: Boolean): String = + if(value) "yes" else "no" -implicit val booleanPrintable: Printable[Boolean] = - new Printable[Boolean] { - def format(value: Boolean): String = - if(value) "yes" else "no" - } final case class Box[A](value: A) ``` ```scala mdoc:silent -implicit def boxPrintable[A](implicit p: Printable[A]): Printable[Box[A]] = +implicit def boxPrintable[A](using p: Printable[A]): Printable[Box[A]] = p.contramap[Box[A]](_.value) ``` @@ -283,10 +276,10 @@ trait Codec[A] { ``` ```scala mdoc:silent -def encode[A](value: A)(implicit c: Codec[A]): String = +def encode[A](value: A)(using c: Codec[A]): String = c.encode(value) -def decode[A](value: String)(implicit c: Codec[A]): A = +def decode[A](value: String)(using c: Codec[A]): A = c.decode(value) ``` @@ -303,21 +296,19 @@ whose `encode` and `decode` methods both simply return the value they are passed: ```scala mdoc:silent -implicit val stringCodec: Codec[String] = - new Codec[String] { - def encode(value: String): String = value - def decode(value: String): String = value - } +given stringCodec: Codec[String] with + def encode(value: String): String = value + def decode(value: String): String = value ``` We can construct many useful `Codecs` for other types by building off of `stringCodec` using `imap`: ```scala mdoc:silent -implicit val intCodec: Codec[Int] = +given intCodec: Codec[Int] = stringCodec.imap(_.toInt, _.toString) -implicit val booleanCodec: Codec[Boolean] = +given booleanCodec: Codec[Boolean] = stringCodec.imap(_.toBoolean, _.toString) ``` @@ -361,22 +352,20 @@ trait Codec[A] { self => ``` ```scala mdoc:invisible -implicit val stringCodec: Codec[String] = - new Codec[String] { - def encode(value: String): String = value - def decode(value: String): String = value - } +given stringCodec: Codec[String] with + def encode(value: String): String = value + def decode(value: String): String = value -implicit val intCodec: Codec[Int] = +given intCodec: Codec[Int] = stringCodec.imap[Int](_.toInt, _.toString) -implicit val booleanCodec: Codec[Boolean] = +given booleanCodec: Codec[Boolean] = stringCodec.imap[Boolean](_.toBoolean, _.toString) -def encode[A](value: A)(implicit c: Codec[A]): String = +def encode[A](value: A)(using c: Codec[A]): String = c.encode(value) -def decode[A](value: String)(implicit c: Codec[A]): A = +def decode[A](value: String)(using c: Codec[A]): A = c.decode(value) ``` @@ -389,7 +378,7 @@ We can implement this using the `imap` method of `stringCodec`: ```scala mdoc:silent -implicit val doubleCodec: Codec[Double] = +given doubleCodec: Codec[Double] = stringCodec.imap[Double](_.toDouble, _.toString) ``` @@ -406,7 +395,7 @@ We create this by calling `imap` on a `Codec[A]`, which we bring into scope using an implicit parameter: ```scala mdoc:silent -implicit def boxCodec[A](implicit c: Codec[A]): Codec[Box[A]] = +implicit def boxCodec[A](using c: Codec[A]): Codec[Box[A]] = c.imap[Box[A]](Box(_), _.value) ``` diff --git a/src/pages/monads/custom-instances.md b/src/pages/monads/custom-instances.md index d9854cc0..58af4651 100644 --- a/src/pages/monads/custom-instances.md +++ b/src/pages/monads/custom-instances.md @@ -167,7 +167,7 @@ the non-tail-recursive solution falls out: ```scala mdoc:silent import cats.Monad -implicit val treeMonad: Monad[Tree] = new Monad[Tree] { +given treeMonad: Monad[Tree] with def pure[A](value: A): Tree[A] = Leaf(value) @@ -188,7 +188,6 @@ implicit val treeMonad: Monad[Tree] = new Monad[Tree] { case Right(value) => Leaf(value) } -} ``` The solution above is perfectly fine for this exercise. @@ -219,7 +218,7 @@ def leaf[A](value: A): Tree[A] = import cats.Monad import scala.annotation.tailrec -implicit val treeMonad: Monad[Tree] = new Monad[Tree] { +given treeMonad: Monad[Tree] with def pure[A](value: A): Tree[A] = Leaf(value) @@ -260,7 +259,6 @@ implicit val treeMonad: Monad[Tree] = new Monad[Tree] { loop(List(func(arg)), Nil).head } -} ``` Regardless of which version of `tailRecM` we define, diff --git a/src/pages/monads/monad-error.md b/src/pages/monads/monad-error.md index ba5c46bc..80996fb3 100644 --- a/src/pages/monads/monad-error.md +++ b/src/pages/monads/monad-error.md @@ -180,14 +180,14 @@ exn.raiseError[Try, Int] Implement a method `validateAdult` with the following signature ```scala -def validateAdult[F[_]](age: Int)(implicit me: MonadError[F, Throwable]): F[Int] = +def validateAdult[F[_]](age: Int)(using me: MonadError[F, Throwable]): F[Int] = ??? ``` When passed an `age` greater than or equal to 18 it should return that value as a success. Otherwise it should return a error represented as an `IllegalArgumentException`. ```scala mdoc:invisible -def validateAdult[F[_]](age: Int)(implicit me: MonadError[F, Throwable]): F[Int] = +def validateAdult[F[_]](age: Int)(using me: MonadError[F, Throwable]): F[Int] = if(age >= 18) age.pure[F] else new IllegalArgumentException("Age must be greater than or equal to 18").raiseError[F, Int] ``` @@ -209,7 +209,7 @@ import cats.MonadError import cats.implicits._ ``` ```scala mdoc:silent -def validateAdult[F[_]](age: Int)(implicit me: MonadError[F, Throwable]): F[Int] = +def validateAdult[F[_]](age: Int)(using me: MonadError[F, Throwable]): F[Int] = if(age >= 18) age.pure[F] else new IllegalArgumentException("Age must be greater than or equal to 18").raiseError[F, Int] ``` diff --git a/src/pages/monoids/cats.md b/src/pages/monoids/cats.md index 8c430d3e..92a3ecae 100644 --- a/src/pages/monoids/cats.md +++ b/src/pages/monoids/cats.md @@ -187,7 +187,7 @@ We can write this as a generic method that accepts an implicit `Monoid` as a par import cats.Monoid import cats.syntax.semigroup._ // for |+| -def add[A](items: List[A])(implicit monoid: Monoid[A]): A = +def add[A](items: List[A])(using monoid: Monoid[A]): A = items.foldLeft(monoid.empty)(_ |+| _) ``` @@ -247,7 +247,7 @@ Make it so! Easy---we simply define a monoid instance for `Order`! ```scala mdoc:silent -implicit val monoid: Monoid[Order] = new Monoid[Order] { +given monoid: Monoid[Order] with def combine(o1: Order, o2: Order) = Order( o1.totalCost + o2.totalCost, @@ -255,6 +255,5 @@ implicit val monoid: Monoid[Order] = new Monoid[Order] { ) def empty = Order(0, 0) -} ``` diff --git a/src/pages/monoids/index.md b/src/pages/monoids/index.md index 46d2f53b..e3fd80a3 100644 --- a/src/pages/monoids/index.md +++ b/src/pages/monoids/index.md @@ -114,13 +114,13 @@ For all values `x`, `y`, and `z`, in `A`, ```scala mdoc:silent def associativeLaw[A](x: A, y: A, z: A) - (implicit m: Monoid[A]): Boolean = { + (using m: Monoid[A]): Boolean = { m.combine(x, m.combine(y, z)) == m.combine(m.combine(x, y), z) } def identityLaw[A](x: A) - (implicit m: Monoid[A]): Boolean = { + (using m: Monoid[A]): Boolean = { (m.combine(x, m.empty) == x) && (m.combine(m.empty, x) == x) } @@ -194,7 +194,7 @@ trait Monoid[A] extends Semigroup[A] { } object Monoid { - def apply[A](implicit monoid: Monoid[A]) = + def apply[A](using monoid: Monoid[A]) = monoid } ``` @@ -204,46 +204,38 @@ There are four monoids for `Boolean`! First, we have *and* with operator `&&` and identity `true`: ```scala mdoc:silent -implicit val booleanAndMonoid: Monoid[Boolean] = - new Monoid[Boolean] { - def combine(a: Boolean, b: Boolean) = a && b - def empty = true - } +given booleanAndMonoid: Monoid[Boolean] with + def combine(a: Boolean, b: Boolean) = a && b + def empty = true ``` Second, we have *or* with operator `||` and identity `false`: ```scala mdoc:silent -implicit val booleanOrMonoid: Monoid[Boolean] = - new Monoid[Boolean] { - def combine(a: Boolean, b: Boolean) = a || b - def empty = false - } +given booleanOrMonoid: Monoid[Boolean] with + def combine(a: Boolean, b: Boolean) = a || b + def empty = false ``` Third, we have *exclusive or* with identity `false`: ```scala mdoc:silent -implicit val booleanEitherMonoid: Monoid[Boolean] = - new Monoid[Boolean] { - def combine(a: Boolean, b: Boolean) = - (a && !b) || (!a && b) +given booleanEitherMonoid: Monoid[Boolean] with + def combine(a: Boolean, b: Boolean) = + (a && !b) || (!a && b) - def empty = false - } + def empty = false ``` Finally, we have *exclusive nor* (the negation of exclusive or) with identity `true`: ```scala mdoc:silent -implicit val booleanXnorMonoid: Monoid[Boolean] = - new Monoid[Boolean] { - def combine(a: Boolean, b: Boolean) = - (!a || b) && (a || !b) +given booleanXnorMonoid: Monoid[Boolean] with + def combine(a: Boolean, b: Boolean) = + (!a || b) && (a || !b) - def empty = true - } + def empty = true ``` Showing that the identity law holds in each case is straightforward. diff --git a/src/pages/monoids/summary.md b/src/pages/monoids/summary.md index bee25e28..f90b1d54 100644 --- a/src/pages/monoids/summary.md +++ b/src/pages/monoids/summary.md @@ -60,7 +60,7 @@ for which we have an instance of `Monoid`: ```scala mdoc:silent def addAll[A](values: List[A]) - (implicit monoid: Monoid[A]): A = + (using monoid: Monoid[A]): A = values.foldRight(monoid.empty)(_ |+| _) ``` diff --git a/src/pages/type-classes/anatomy.md b/src/pages/type-classes/anatomy.md index f61cb34a..bd5f8f39 100644 --- a/src/pages/type-classes/anatomy.md +++ b/src/pages/type-classes/anatomy.md @@ -59,20 +59,16 @@ and tagging them with the `implicit` keyword: final case class Person(name: String, email: String) object JsonWriterInstances { - implicit val stringWriter: JsonWriter[String] = - new JsonWriter[String] { - def write(value: String): Json = - JsString(value) - } - - implicit val personWriter: JsonWriter[Person] = - new JsonWriter[Person] { - def write(value: Person): Json = - JsObject(Map( - "name" -> JsString(value.name), - "email" -> JsString(value.email) - )) - } + given stringWriter: JsonWriter[String] with + def write(value: String): Json = + JsString(value) + + given personWriter: JsonWriter[Person] with + def write(value: Person): Json = + JsObject(Map( + "name" -> JsString(value.name), + "email" -> JsString(value.email) + )) // etc... } @@ -99,7 +95,7 @@ is to place methods in a singleton object: ```scala mdoc:silent object Json { - def toJson[A](value: A)(implicit w: JsonWriter[A]): Json = + def toJson[A](value: A)(using w: JsonWriter[A]): Json = w.write(value) } ``` @@ -108,7 +104,7 @@ To use this object, we import any type class instances we care about and call the relevant method: ```scala mdoc:silent -import JsonWriterInstances._ +import JsonWriterInstances.personWriter ``` ```scala mdoc @@ -121,7 +117,7 @@ It tries to fix this by searching for type class instances of the relevant types and inserting them at the call site: ```scala mdoc:silent -Json.toJson(Person("Dave", "dave@example.com"))(personWriter) +Json.toJson(Person("Dave", "dave@example.com"))(using personWriter) ``` **Interface Syntax** @@ -137,7 +133,7 @@ These are older terms that we don't use anymore. ```scala mdoc:silent object JsonSyntax { implicit class JsonWriterOps[A](value: A) { - def toJson(implicit w: JsonWriter[A]): Json = + def toJson(using w: JsonWriter[A]): Json = w.write(value) } } @@ -147,7 +143,7 @@ We use interface syntax by importing it alongside the instances for the types we need: ```scala mdoc:silent -import JsonWriterInstances._ +import JsonWriterInstances.personWriter import JsonSyntax._ ``` @@ -159,7 +155,7 @@ Again, the compiler searches for candidates for the implicit parameters and fills them in for us: ```scala mdoc:silent -Person("Dave", "dave@example.com").toJson(personWriter) +Person("Dave", "dave@example.com").toJson(using personWriter) ``` **The *implicitly* Method** @@ -169,7 +165,7 @@ a generic type class interface called `implicitly`. Its definition is very simple: ```scala -def implicitly[A](implicit value: A): A = +def implicitly[A](using value: A): A = value ``` @@ -177,7 +173,7 @@ We can use `implicitly` to summon any value from implicit scope. We provide the type we want and `implicitly` does the rest: ```scala mdoc -import JsonWriterInstances._ +import JsonWriterInstances.stringWriter implicitly[JsonWriter[String]] ``` diff --git a/src/pages/type-classes/cats.md b/src/pages/type-classes/cats.md index 6e53501d..5af9b393 100644 --- a/src/pages/type-classes/cats.md +++ b/src/pages/type-classes/cats.md @@ -123,11 +123,9 @@ simply by implementing the trait for a given type: ```scala mdoc:silent import java.util.Date -implicit val dateShow: Show[Date] = - new Show[Date] { - def show(date: Date): String = - s"${date.getTime}ms since the epoch." - } +given dateShow: Show[Date] with + def show(date: Date): String = + s"${date.getTime}ms since the epoch." ``` ```scala mdoc new Date().show @@ -158,7 +156,7 @@ import cats.Show import java.util.Date ``` ```scala mdoc:silent -implicit val dateShow: Show[Date] = +given dateShow: Show[Date] = Show.show(date => s"${date.getTime}ms since the epoch.") ``` @@ -196,7 +194,7 @@ In the companion object we replace our `Printable` with an instance of `Show` using one of the definition helpers discussed above: ```scala mdoc:silent -implicit val catShow: Show[Cat] = Show.show[Cat] { cat => +given catShow: Show[Cat] = Show.show[Cat] { cat => val name = cat.name.show val age = cat.age.show val color = cat.color.show diff --git a/src/pages/type-classes/equal.md b/src/pages/type-classes/equal.md index 14340a9d..f9343147 100644 --- a/src/pages/type-classes/equal.md +++ b/src/pages/type-classes/equal.md @@ -151,7 +151,7 @@ import cats.instances.long._ // for Eq ``` ```scala mdoc:silent -implicit val dateEq: Eq[Date] = +given dateEq: Eq[Date] = Eq.instance[Date] { (date1, date2) => date1.getTime === date2.getTime } @@ -209,7 +209,7 @@ into scope for the implementation of `Eq[Cat]`: import cats.instances.int._ // for Eq import cats.instances.string._ // for Eq -implicit val catEqual: Eq[Cat] = +given catEqual: Eq[Cat] = Eq.instance[Cat] { (cat1, cat2) => (cat1.name === cat2.name ) && (cat1.age === cat2.age ) && diff --git a/src/pages/type-classes/implicits.md b/src/pages/type-classes/implicits.md index 1f179e0f..3167f2e4 100644 --- a/src/pages/type-classes/implicits.md +++ b/src/pages/type-classes/implicits.md @@ -16,28 +16,24 @@ trait JsonWriter[A] { case class Person(name: String, email: String) object JsonWriterInstances { - implicit val stringWriter: JsonWriter[String] = - new JsonWriter[String] { - def write(value: String): Json = - JsString(value) - } - - implicit val personWriter: JsonWriter[Person] = - new JsonWriter[Person] { - def write(value: Person): Json = - JsObject(Map( - "name" -> JsString(value.name), - "email" -> JsString(value.email) - )) - } + given stringWriter: JsonWriter[String] with + def write(value: String): Json = + JsString(value) + + given personWriter: JsonWriter[Person] with + def write(value: Person): Json = + JsObject(Map( + "name" -> JsString(value.name), + "email" -> JsString(value.email) + )) // etc... } -import JsonWriterInstances._ +import JsonWriterInstances.{personWriter, stringWriter} object Json { - def toJson[A](value: A)(implicit w: JsonWriter[A]): Json = + def toJson[A](value: A)(using w: JsonWriter[A]): Json = w.write(value) } ``` @@ -104,23 +100,21 @@ trait JsonWriter[A] { } object JsonWriterInstances { - implicit val stringWriter: JsonWriter[String] = - new JsonWriter[String] { - def write(value: String): Json = - JsString(value) - } + given stringWriter: JsonWriter[String] with + def write(value: String): Json = + JsString(value) } object Json { - def toJson[A](value: A)(implicit w: JsonWriter[A]): Json = + def toJson[A](value: A)(using w: JsonWriter[A]): Json = w.write(value) } ``` ```scala mdoc:fail -implicit val writer1: JsonWriter[String] = +given writer1: JsonWriter[String] = JsonWriterInstances.stringWriter -implicit val writer2: JsonWriter[String] = +given writer2: JsonWriter[String] = JsonWriterInstances.stringWriter Json.toJson("A string") @@ -179,10 +173,10 @@ We could try to brute force the problem by creating a library of `implicit vals`: ```scala -implicit val optionIntWriter: JsonWriter[Option[Int]] = +given optionIntWriter: JsonWriter[Option[Int]] with ??? -implicit val optionPersonWriter: JsonWriter[Option[Person]] = +given optionPersonWriter: JsonWriter[Option[Person]] with ??? // and so on... @@ -205,7 +199,7 @@ Here is the same code written out as an `implicit def`: ```scala mdoc:silent implicit def optionWriter[A] - (implicit writer: JsonWriter[A]): JsonWriter[Option[A]] = + (using writer: JsonWriter[A]): JsonWriter[Option[A]] = new JsonWriter[Option[A]] { def write(option: Option[A]): Json = option match { @@ -221,7 +215,7 @@ fill in the `A`-specific functionality. When the compiler sees an expression like this: ```scala mdoc:invisible -import JsonWriterInstances._ +import JsonWriterInstances.stringWriter ``` ```scala mdoc:silent Json.toJson(Option("A string")) @@ -231,14 +225,14 @@ it searches for an implicit `JsonWriter[Option[String]]`. It finds the implicit method for `JsonWriter[Option[A]]`: ```scala mdoc:silent -Json.toJson(Option("A string"))(optionWriter[String]) +Json.toJson(Option("A string"))(using optionWriter[String]) ``` and recursively searches for a `JsonWriter[String]` to use as the parameter to `optionWriter`: ```scala mdoc:silent -Json.toJson(Option("A string"))(optionWriter(stringWriter)) +Json.toJson(Option("A string"))(using optionWriter(using stringWriter)) ``` In this way, implicit resolution becomes diff --git a/src/pages/type-classes/printable.md b/src/pages/type-classes/printable.md index 609fe4c9..aa887cd7 100644 --- a/src/pages/type-classes/printable.md +++ b/src/pages/type-classes/printable.md @@ -39,13 +39,11 @@ and package them in `PrintableInstances`: ```scala mdoc:silent object PrintableInstances { - implicit val stringPrintable: Printable[String] = new Printable[String] { + given stringPrintable: Printable[String] with def format(input: String) = input - } - implicit val intPrintable: Printable[Int] = new Printable[Int] { + given intPrintable: Printable[Int] with def format(input: Int) = input.toString - } } ``` @@ -53,10 +51,10 @@ Finally we define an *interface* object, `Printable`: ```scala mdoc:silent object Printable { - def format[A](input: A)(implicit p: Printable[A]): String = + def format[A](input: A)(using p: Printable[A]): String = p.format(input) - def print[A](input: A)(implicit p: Printable[A]): Unit = + def print[A](input: A)(using p: Printable[A]): Unit = println(p.format(input)) } ``` @@ -104,16 +102,15 @@ These either go into the companion object of `Cat` or a separate object to act as a namespace: ```scala mdoc:silent -import PrintableInstances._ +import PrintableInstances.{intPrintable, stringPrintable} -implicit val catPrintable: Printable[Cat] = new Printable[Cat] { +given catPrintable: Printable[Cat] with def format(cat: Cat) = { val name = Printable.format(cat.name) val age = Printable.format(cat.age) val color = Printable.format(cat.color) s"$name is a $age year-old $color cat." } -} ``` Finally, we use the type class by @@ -157,11 +154,11 @@ First we define an `implicit class` containing our extension methods: ```scala mdoc:silent object PrintableSyntax { implicit class PrintableOps[A](value: A) { - def format(implicit p: Printable[A]): String = + def format(using p: Printable[A]): String = p.format(value) - def print(implicit p: Printable[A]): Unit = - println(format(p)) + def print(using p: Printable[A]): Unit = + println(format(using p)) } } ``` From 95f483cb8e693c0897ee13857caa6e9c27eec0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 12:33:37 -0600 Subject: [PATCH 10/33] =?UTF-8?q?implicit=20def=20=E2=86=92=20given?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/case-studies/crdt/abstraction.md | 38 +++++++++---------- src/pages/case-studies/crdt/generalisation.md | 14 +++---- src/pages/functors/contravariant-invariant.md | 6 +-- src/pages/monoids/index.md | 26 +++++-------- 4 files changed, 36 insertions(+), 48 deletions(-) diff --git a/src/pages/case-studies/crdt/abstraction.md b/src/pages/case-studies/crdt/abstraction.md index 6e3d32b2..74e48542 100644 --- a/src/pages/case-studies/crdt/abstraction.md +++ b/src/pages/case-studies/crdt/abstraction.md @@ -40,14 +40,12 @@ object BoundedSemiLattice { val empty: Int = 0 - implicit def setInstance[A]: BoundedSemiLattice[Set[A]] = - new BoundedSemiLattice[Set[A]]{ - def combine(a1: Set[A], a2: Set[A]): Set[A] = - a1 union a2 + given setInstance[A]: BoundedSemiLattice[Set[A]] with + def combine(a1: Set[A], a2: Set[A]): Set[A] = + a1 union a2 - val empty: Set[A] = - Set.empty[A] - } + val empty: Set[A] = + Set.empty[A] } ``` ```scala mdoc:silent @@ -86,22 +84,20 @@ import cats.instances.map._ // for Monoid import cats.syntax.semigroup._ // for |+| import cats.syntax.foldable._ // for combineAll -implicit def mapGCounterInstance[K, V]: GCounter[Map, K, V] = - new GCounter[Map, K, V] { - def increment(map: Map[K, V])(key: K, value: V) - (using m: CommutativeMonoid[V]): Map[K, V] = { - val total = map.getOrElse(key, m.empty) |+| value - map + (key -> total) - } +given mapGCounterInstance[K, V]: GCounter[Map, K, V] with + def increment(map: Map[K, V])(key: K, value: V) + (using m: CommutativeMonoid[V]): Map[K, V] = { + val total = map.getOrElse(key, m.empty) |+| value + map + (key -> total) + } - def merge(map1: Map[K, V], map2: Map[K, V]) - (using b: BoundedSemiLattice[V]): Map[K, V] = - map1 |+| map2 + def merge(map1: Map[K, V], map2: Map[K, V]) + (using b: BoundedSemiLattice[V]): Map[K, V] = + map1 |+| map2 - def total(map: Map[K, V]) - (using m: CommutativeMonoid[V]): V = - map.values.toList.combineAll - } + def total(map: Map[K, V]) + (using m: CommutativeMonoid[V]): V = + map.values.toList.combineAll ``` diff --git a/src/pages/case-studies/crdt/generalisation.md b/src/pages/case-studies/crdt/generalisation.md index 5b9f9e0a..6dd60bc8 100644 --- a/src/pages/case-studies/crdt/generalisation.md +++ b/src/pages/case-studies/crdt/generalisation.md @@ -160,14 +160,12 @@ object wrapper { val empty: Int = 0 - implicit def setInstance[A]: BoundedSemiLattice[Set[A]] = - new BoundedSemiLattice[Set[A]]{ - def combine(a1: Set[A], a2: Set[A]): Set[A] = - a1 union a2 - - val empty: Set[A] = - Set.empty[A] - } + given setInstance[A]: BoundedSemiLattice[Set[A]] with + def combine(a1: Set[A], a2: Set[A]): Set[A] = + a1 union a2 + + val empty: Set[A] = + Set.empty[A] } }; import wrapper._ ``` diff --git a/src/pages/functors/contravariant-invariant.md b/src/pages/functors/contravariant-invariant.md index f2f60ca1..42cb754a 100644 --- a/src/pages/functors/contravariant-invariant.md +++ b/src/pages/functors/contravariant-invariant.md @@ -143,7 +143,7 @@ create your instance from an existing instance using `contramap`. ```scala mdoc:invisible -implicit def boxPrintable[A](using p: Printable[A]): Printable[Box[A]] = +given boxPrintable[A](using p: Printable[A]): Printable[Box[A]] = p.contramap[Box[A]](_.value) ``` @@ -220,7 +220,7 @@ given booleanPrintable: Printable[Boolean] with final case class Box[A](value: A) ``` ```scala mdoc:silent -implicit def boxPrintable[A](using p: Printable[A]): Printable[Box[A]] = +given boxPrintable[A](using p: Printable[A]): Printable[Box[A]] = p.contramap[Box[A]](_.value) ``` @@ -395,7 +395,7 @@ We create this by calling `imap` on a `Codec[A]`, which we bring into scope using an implicit parameter: ```scala mdoc:silent -implicit def boxCodec[A](using c: Codec[A]): Codec[Box[A]] = +given boxCodec[A](using c: Codec[A]): Codec[Box[A]] = c.imap[Box[A]](Box(_), _.value) ``` diff --git a/src/pages/monoids/index.md b/src/pages/monoids/index.md index e3fd80a3..f78ae45c 100644 --- a/src/pages/monoids/index.md +++ b/src/pages/monoids/index.md @@ -251,11 +251,9 @@ What monoids and semigroups are there for sets? *Set union* forms a monoid along with the empty set: ```scala mdoc:silent -implicit def setUnionMonoid[A]: Monoid[Set[A]] = - new Monoid[Set[A]] { - def combine(a: Set[A], b: Set[A]) = a union b - def empty = Set.empty[A] - } +given setUnionMonoid[A]: Monoid[Set[A]] with + def combine(a: Set[A], b: Set[A]) = a union b + def empty = Set.empty[A] ``` We need to define `setUnionMonoid` as a method @@ -277,11 +275,9 @@ Set intersection forms a semigroup, but doesn't form a monoid because it has no identity element: ```scala mdoc:silent -implicit def setIntersectionSemigroup[A]: Semigroup[Set[A]] = - new Semigroup[Set[A]] { - def combine(a: Set[A], b: Set[A]) = - a intersect b - } +given setIntersectionSemigroup[A]: Semigroup[Set[A]] with + def combine(a: Set[A], b: Set[A]) = + a intersect b ``` Set complement and set difference are not associative, @@ -293,11 +289,9 @@ does also form a monoid with the empty set: import cats.Monoid ``` ```scala mdoc:silent -implicit def symDiffMonoid[A]: Monoid[Set[A]] = - new Monoid[Set[A]] { - def combine(a: Set[A], b: Set[A]): Set[A] = - (a diff b) union (b diff a) - def empty: Set[A] = Set.empty - } +given symDiffMonoid[A]: Monoid[Set[A]] with + def combine(a: Set[A], b: Set[A]): Set[A] = + (a diff b) union (b diff a) + def empty: Set[A] = Set.empty ``` From e503659fd9ed2f55f13dc9ab359434cdf2c8bd36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 12:48:23 -0600 Subject: [PATCH 11/33] =?UTF-8?q?implicit=20class=20=E2=86=92=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/case-studies/crdt/abstraction.md | 2 +- src/pages/functors/cats.md | 2 +- src/pages/type-classes/anatomy.md | 9 +++------ src/pages/type-classes/printable.md | 16 +++++----------- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/pages/case-studies/crdt/abstraction.md b/src/pages/case-studies/crdt/abstraction.md index 74e48542..5fd1c71a 100644 --- a/src/pages/case-studies/crdt/abstraction.md +++ b/src/pages/case-studies/crdt/abstraction.md @@ -174,7 +174,7 @@ With our type class in place we can implement syntax to enhance data types for which we have instances: ```scala mdoc:silent -implicit class KvsOps[F[_,_], K, V](f: F[K, V]) { +extension [F[_,_], K, V](f: F[K, V]) { def put(key: K, value: V) (using kvs: KeyValueStore[F]): F[K, V] = kvs.put(f)(key, value) diff --git a/src/pages/functors/cats.md b/src/pages/functors/cats.md index 24f0d461..9f147812 100644 --- a/src/pages/functors/cats.md +++ b/src/pages/functors/cats.md @@ -101,7 +101,7 @@ the `map` method in `cats.syntax.functor`. Here's a simplified version of the code: ```scala -implicit class FunctorOps[F[_], A](src: F[A]) { +extension [F[_], A](src: F[A]) { def map[B](func: A => B) (using functor: Functor[F]): F[B] = functor.map(src)(func) diff --git a/src/pages/type-classes/anatomy.md b/src/pages/type-classes/anatomy.md index bd5f8f39..ab75ad81 100644 --- a/src/pages/type-classes/anatomy.md +++ b/src/pages/type-classes/anatomy.md @@ -131,11 +131,9 @@ referred to as "type enrichment" or "pimping". These are older terms that we don't use anymore. ```scala mdoc:silent -object JsonSyntax { - implicit class JsonWriterOps[A](value: A) { - def toJson(using w: JsonWriter[A]): Json = - w.write(value) - } +extension [A](value: A) { + def toJson(using w: JsonWriter[A]): Json = + w.write(value) } ``` @@ -144,7 +142,6 @@ alongside the instances for the types we need: ```scala mdoc:silent import JsonWriterInstances.personWriter -import JsonSyntax._ ``` ```scala mdoc diff --git a/src/pages/type-classes/printable.md b/src/pages/type-classes/printable.md index aa887cd7..806c110a 100644 --- a/src/pages/type-classes/printable.md +++ b/src/pages/type-classes/printable.md @@ -152,14 +152,12 @@ by defining some extension methods to provide better syntax: First we define an `implicit class` containing our extension methods: ```scala mdoc:silent -object PrintableSyntax { - implicit class PrintableOps[A](value: A) { - def format(using p: Printable[A]): String = - p.format(value) +extension [A](value: A) { + def format(using p: Printable[A]): String = + p.format(value) - def print(using p: Printable[A]): Unit = - println(format(using p)) - } + def print(using p: Printable[A]): Unit = + println(format(using p)) } ``` @@ -167,10 +165,6 @@ With `PrintableOps` in scope, we can call the imaginary `print` and `format` methods on any value for which Scala can locate an implicit instance of `Printable`: -```scala mdoc:silent -import PrintableSyntax._ -``` - ```scala mdoc Cat("Garfield", 41, "ginger and black").print ``` From 2096d331b2886793bbc44c7c14a339698aae961a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 12:49:07 -0600 Subject: [PATCH 12/33] Overhaul some text regarding implicits --- src/pages/case-studies/parser/applicative.md | 4 ++-- src/pages/type-classes/anatomy.md | 10 +++++----- src/pages/type-classes/printable.md | 17 +++++++---------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/pages/case-studies/parser/applicative.md b/src/pages/case-studies/parser/applicative.md index 671e622c..1b6873f2 100644 --- a/src/pages/case-studies/parser/applicative.md +++ b/src/pages/case-studies/parser/applicative.md @@ -15,11 +15,11 @@ Given a nested tuple like `((1, 2), 3)` we could write an implicit conversion to ### Exercise: Flattening -Define a `Tuple2Flatten` implicit class with an extension method `flatten`, and an instance of `Flattener` for a nested tuple like `((1, 2), 3)`. +Define an extension method `flatten`, and an instance of `Flattener` for a nested tuple like `((1, 2), 3)`.
~~~ scala -implicit class Tuple2Flatten[A, B, C](val in: ((A, B), C)) extends AnyVal { +extension [A, B, C](in: ((A, B), C)) { def flatten: (A, B, C) = in match { case ((a, b), c) => (a, b, c) diff --git a/src/pages/type-classes/anatomy.md b/src/pages/type-classes/anatomy.md index ab75ad81..5d0ea8d2 100644 --- a/src/pages/type-classes/anatomy.md +++ b/src/pages/type-classes/anatomy.md @@ -5,14 +5,14 @@ the *type class* itself, *instances* for particular types, and the methods that *use* type classes. -Type classes in Scala are implemented using *implicit values* and *parameters*, -and optionally using *implicit classes*. +Type classes in Scala are implemented using *traits*, *given instances* and *using clauses*, +and optionally using *extension methods*. Scala language constructs correspond to the components of type classes as follows: - traits: type classes; -- implicit values: type class instances; -- implicit parameters: type class use; and -- implicit classes: optional utilities that make type classes easier to use. +- given instances: type class instances; +- using clauses: type class use; and +- extension methods: optional utilities that make type classes easier to use. Let's see how this works in detail. diff --git a/src/pages/type-classes/printable.md b/src/pages/type-classes/printable.md index 806c110a..077fc023 100644 --- a/src/pages/type-classes/printable.md +++ b/src/pages/type-classes/printable.md @@ -132,24 +132,21 @@ Printable.print(cat) Let's make our printing library easier to use by defining some extension methods to provide better syntax: - 1. Create an object called `PrintableSyntax`. + 1. Define an `extension [A](value: A)` to wrap up a value of type `A`. - 2. Inside `PrintableSyntax` define an `implicit class PrintableOps[A]` - to wrap up a value of type `A`. + 2. Define the following extension methods: - 3. In `PrintableOps` define the following methods: - - - `format` accepts an implicit `Printable[A]` + - `format` using an implicit `Printable[A]` and returns a `String` representation of the wrapped `A`; - - `print` accepts an implicit `Printable[A]` and returns `Unit`. + - `print` using an implicit `Printable[A]` and returns `Unit`. It prints the wrapped `A` to the console. - 4. Use the extension methods to print the example `Cat` + 3. Use the extension methods to print the example `Cat` you created in the previous exercise.
-First we define an `implicit class` containing our extension methods: +First we define our extension methods: ```scala mdoc:silent extension [A](value: A) { @@ -161,7 +158,7 @@ extension [A](value: A) { } ``` -With `PrintableOps` in scope, +With the extensions in scope, we can call the imaginary `print` and `format` methods on any value for which Scala can locate an implicit instance of `Printable`: From b220a4bb37101f4cab1c616c743e7d0d09c9a703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 12:53:33 -0600 Subject: [PATCH 13/33] remove typelevel catalysts since it was deprecated in 2019 --- src/pages/links.md | 1 - src/pages/preface/versions.md | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/src/pages/links.md b/src/pages/links.md index 7a63fe53..bd3baf76 100644 --- a/src/pages/links.md +++ b/src/pages/links.md @@ -123,7 +123,6 @@ [link-phil-freeman-tailrecm]: http://functorial.com/stack-safety-for-free/index.pdf [link-play-json-format]: https://www.playframework.com/documentation/2.6.x/ScalaJsonCombinators#Format [link-sbt]: http://www.scala-sbt.org/ -[link-sbt-catalysts]: https://github.com/typelevel/sbt-catalysts [link-scalactic]: http://scalactic.org [link-scalaz-contrib]: https://github.com/typelevel/scalaz-contrib [link-scodec-codec]: http://scodec.org/guide/Core+Algebra.html#Codec diff --git a/src/pages/preface/versions.md b/src/pages/preface/versions.md index 4ebecc72..9aefbc41 100644 --- a/src/pages/preface/versions.md +++ b/src/pages/preface/versions.md @@ -33,19 +33,3 @@ with Cats as a dependency. See the generated `README.md` for instructions on how to run the sample code and/or start an interactive Scala console. - -The `cats-seed` template is very minimal. -If you'd prefer a more batteries-included starting point, -check out Typelevel's `sbt-catalysts` template: - -```bash -$ sbt new typelevel/sbt-catalysts.g8 -``` - -This will generate a project with a suite -of library dependencies and compiler plugins, -together with templates for unit tests -and documentation. -See the project pages for [catalysts][link-catalysts] -and [sbt-catalysts][link-sbt-catalysts] -for more information. From 9bfe988b3b34f94038b82886826e02cbd1c11a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 13:57:55 -0600 Subject: [PATCH 14/33] =?UTF-8?q?sealed=20trait/case=20[class|object]=20?= =?UTF-8?q?=E2=86=92=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../case-studies/parser/error-handling.md | 18 ++++++------- src/pages/case-studies/parser/transforms.md | 27 +++++++------------ src/pages/functors/cats.md | 11 ++++---- src/pages/monads/custom-instances.md | 10 +++---- src/pages/monads/either.md | 16 +++++------ src/pages/type-classes/anatomy.md | 13 +++++---- src/pages/type-classes/implicits.md | 26 +++++++++++------- src/pages/type-classes/instance-selection.md | 7 +++-- 8 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/pages/case-studies/parser/error-handling.md b/src/pages/case-studies/parser/error-handling.md index fb6c4b60..8296fe6c 100644 --- a/src/pages/case-studies/parser/error-handling.md +++ b/src/pages/case-studies/parser/error-handling.md @@ -12,18 +12,17 @@ We will fix this by thinking and coding systematically. The first thing is ask o Once we make this realisation the code follows straight-away. For this type of data we use the *sealed trait* pattern. -### The Sealed Trait Pattern +### Enum Pattern If some data `A` can be a `B` or a `C` and nothing else, we should write ~~~ scala -sealed trait A -final case class B() extends A -final case class C() extends A +enum A { + case B() + case C() +} ~~~ -Sealed traits. Extension points (final / non-final) - ### Exercise ### Better ParserResult @@ -35,9 +34,10 @@ Implement a better `ParserResult`. This exercise is deliberately vague about wha Here's my implementation. It follows the existing pattern for success, maintaining the `result` and `remainder` fields, but returns an error message on failure. I also renamed `ParseResult` to just `Parse`. With the subtypes it's fairly obvious that a `Parse` is the result of a `Parser`. ~~~ scala -sealed trait Parse -final case class Failure(message: String) extends Parse -final case class Success(result: String, remainder: String) extends Parse +enum Parse { + case class Failure(message: String) + case class Success(result: String, remainder: String) +} ~~~
diff --git a/src/pages/case-studies/parser/transforms.md b/src/pages/case-studies/parser/transforms.md index 49da1360..be938e28 100644 --- a/src/pages/case-studies/parser/transforms.md +++ b/src/pages/case-studies/parser/transforms.md @@ -101,10 +101,11 @@ Start by implementing data structures to store the result of a successful parse. An expression is an addition, or a substraction, or a number. Once we have this structure its realisation in code is straightforward. ~~~ scala -sealed trait Expression -final case class Addition(left: Expression, right: Expression) extends Expression -final case class Subtraction(left: Expression, right: Expression) extends Expression -final case class Number(value: Int) extends Expression +enum Expression { + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Number(value: Int) +} ~~~
@@ -147,20 +148,10 @@ There are two ways we could write this method: as a method on `Expression` using Either way, `eval` is a straightforward application of structural recursion. My implementation is: ~~~ scala -sealed trait Expression { - def eval: Int -} -final case class Addition(left: Expression, right: Expression) extends Expression { - def eval: Int = - left.eval + right.eval -} -final case class Subtraction(left: Expression, right: Expression) extends Expression { - def eval: Int = - left.eval - right.eval -} -final case class Number(value: Int) extends Expression { - def eval: Int = - value +enum Expression(val eval: Int) { + case Addition(left: Expression, right: Expression) extends Expression(left.eval + right.eval) + case Subtraction(left: Expression, right: Expression) extends Expression(left.eval - right.eval) + case Number(value: Int) extends Expression(value) } ~~~ diff --git a/src/pages/functors/cats.md b/src/pages/functors/cats.md index 9f147812..67cd5859 100644 --- a/src/pages/functors/cats.md +++ b/src/pages/functors/cats.md @@ -200,12 +200,10 @@ Write a `Functor` for the following binary tree data type. Verify that the code works as expected on instances of `Branch` and `Leaf`: ```scala mdoc:silent -sealed trait Tree[+A] - -final case class Branch[A](left: Tree[A], right: Tree[A]) - extends Tree[A] - -final case class Leaf[A](value: A) extends Tree[A] +enum Tree[+A] { + case Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A] + case Leaf[A](value: A) extends Tree[A] +} ```
@@ -216,6 +214,7 @@ with the same pattern of `Branch` and `Leaf` nodes: ```scala mdoc:silent import cats.Functor +import Tree.{Branch, Leaf} given treeFunctor: Functor[Tree] with def map[A, B](tree: Tree[A])(func: A => B): Tree[B] = diff --git a/src/pages/monads/custom-instances.md b/src/pages/monads/custom-instances.md index 58af4651..efb437fa 100644 --- a/src/pages/monads/custom-instances.md +++ b/src/pages/monads/custom-instances.md @@ -127,12 +127,12 @@ Let's write a `Monad` for our `Tree` data type from last chapter. Here's the type again: ```scala mdoc:silent -sealed trait Tree[+A] - -final case class Branch[A](left: Tree[A], right: Tree[A]) - extends Tree[A] +enum Tree[+A] { + case Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A] + case Leaf[A](value: A) extends Tree[A] +} -final case class Leaf[A](value: A) extends Tree[A] +import Tree.{Branch, Leaf} def branch[A](left: Tree[A], right: Tree[A]): Tree[A] = Branch(left, right) diff --git a/src/pages/monads/either.md b/src/pages/monads/either.md index 8990016e..3eb5fdf1 100644 --- a/src/pages/monads/either.md +++ b/src/pages/monads/either.md @@ -236,16 +236,14 @@ to represent errors that may occur in our program: ```scala mdoc:silent object wrapper { - sealed trait LoginError extends Product with Serializable - - final case class UserNotFound(username: String) - extends LoginError - - final case class PasswordIncorrect(username: String) - extends LoginError - - case object UnexpectedError extends LoginError + enum LoginError { + case UserNotFound(username: String) + case PasswordIncorrect(username: String) + case UnexpectedError + } }; import wrapper._ + +import LoginError.* ``` ```scala mdoc:silent diff --git a/src/pages/type-classes/anatomy.md b/src/pages/type-classes/anatomy.md index 5d0ea8d2..3ebc6f30 100644 --- a/src/pages/type-classes/anatomy.md +++ b/src/pages/type-classes/anatomy.md @@ -27,11 +27,14 @@ as follows: ```scala mdoc:silent:reset-object // Define a very simple JSON AST -sealed trait Json -final case class JsObject(get: Map[String, Json]) extends Json -final case class JsString(get: String) extends Json -final case class JsNumber(get: Double) extends Json -final case object JsNull extends Json +enum Json { + case JsObject(get: Map[String, Json]) + case JsString(get: String) + case JsNumber(get: Double) + case JsNull +} + +import Json.* // The "serialize to JSON" behaviour is encoded in this trait trait JsonWriter[A] { diff --git a/src/pages/type-classes/implicits.md b/src/pages/type-classes/implicits.md index 3167f2e4..09bdba73 100644 --- a/src/pages/type-classes/implicits.md +++ b/src/pages/type-classes/implicits.md @@ -3,11 +3,14 @@ ```scala mdoc:invisible // Forward definitions -sealed trait Json -case class JsObject(get: Map[String, Json]) extends Json -case class JsString(get: String) extends Json -case class JsNumber(get: Double) extends Json -case object JsNull extends Json +enum Json { + case JsObject(get: Map[String, Json]) + case JsString(get: String) + case JsNumber(get: Double) + case JsNull +} + +import Json.* trait JsonWriter[A] { def write(value: A): Json @@ -89,11 +92,14 @@ Furthermore, if the compiler sees multiple candidate definitions, it fails with an *ambiguous implicit values* error: ```scala mdoc:invisible:reset-object -sealed trait Json -case class JsObject(get: Map[String, Json]) extends Json -case class JsString(get: String) extends Json -case class JsNumber(get: Double) extends Json -case object JsNull extends Json +enum Json { + case JsObject(get: Map[String, Json]) + case JsString(get: String) + case JsNumber(get: Double) + case JsNull +} + +import Json.* trait JsonWriter[A] { def write(value: A): Json diff --git a/src/pages/type-classes/instance-selection.md b/src/pages/type-classes/instance-selection.md index 00f953c3..16a3bf7a 100644 --- a/src/pages/type-classes/instance-selection.md +++ b/src/pages/type-classes/instance-selection.md @@ -59,8 +59,11 @@ anywhere we expect a `List[Shape]` because `Circle` is a subtype of `Shape`: ```scala mdoc:silent -sealed trait Shape -case class Circle(radius: Double) extends Shape +enum Shape { + case Circle(radius: Double) +} + +import Shape.Circle ``` ```scala From f638e778440c41e50893973033a9ee221dc6ec20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 14:47:35 -0600 Subject: [PATCH 15/33] Scala 3 overhaul to 2.6 --- src/pages/adt/scala.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pages/adt/scala.md b/src/pages/adt/scala.md index 3193a124..93e93998 100644 --- a/src/pages/adt/scala.md +++ b/src/pages/adt/scala.md @@ -20,10 +20,8 @@ Not everyone makes their case classes `final`, but they should. A non-`final` ca A logical or (a sum type) is represented by an `enum`. For the sum type `A` is a `B` **or** `C` the Scala 3 representation is ```scala -enum A { - case B - case C -} +enum A: + case B, C ``` There are a few wrinkles to be aware of. @@ -37,10 +35,9 @@ If we have a sum of products, such as: the representation is ```scala -enum A { +enum A: case B(d: D, e: E) case C(f: F, g: G) -} ``` In other words you can't write `final case class` inside an `enum`. You also can't nest `enum` inside `enum`. Nested logical ors can be rewritten into a single logical or containing only logical ands (known as disjunctive normal form) so this is not a limitation in practice. However the Scala 2 representation is still available in Scala 3 should you want more expressivity. From ad913a092a51275da95c7d5cb4d82a02a96f33b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 14:47:46 -0600 Subject: [PATCH 16/33] Scala 3 overhaul to 3.1 --- src/pages/type-classes/anatomy.md | 59 +++++++++++-------------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/src/pages/type-classes/anatomy.md b/src/pages/type-classes/anatomy.md index 3ebc6f30..d892e252 100644 --- a/src/pages/type-classes/anatomy.md +++ b/src/pages/type-classes/anatomy.md @@ -27,19 +27,15 @@ as follows: ```scala mdoc:silent:reset-object // Define a very simple JSON AST -enum Json { +enum Json: case JsObject(get: Map[String, Json]) case JsString(get: String) case JsNumber(get: Double) case JsNull -} - -import Json.* // The "serialize to JSON" behaviour is encoded in this trait -trait JsonWriter[A] { +trait JsonWriter[A]: def write(value: A): Json -} ``` `JsonWriter` is our type class in this example, @@ -56,36 +52,33 @@ and types from our domain model. In Scala we define instances by creating concrete implementations of the type class -and tagging them with the `implicit` keyword: +and tagging them with the `given` keyword: ```scala mdoc:silent final case class Person(name: String, email: String) -object JsonWriterInstances { - given stringWriter: JsonWriter[String] with - def write(value: String): Json = - JsString(value) +given stringWriter: JsonWriter[String] with + def write(value: String): Json = + Json.JsString(value) - given personWriter: JsonWriter[Person] with - def write(value: Person): Json = - JsObject(Map( - "name" -> JsString(value.name), - "email" -> JsString(value.email) - )) +given personWriter: JsonWriter[Person] with + def write(value: Person): Json = + Json.JsObject(Map( + "name" -> Json.JsString(value.name), + "email" -> Json.JsString(value.email) + )) - // etc... -} +// etc... ``` -These are known as implicit values. - +These are known as given instances. ### Type Class Use A type class *use* is any functionality that requires a type class instance to work. In Scala this means any method -that accepts instances of the type class as implicit parameters. +that accepts instances of the type class as using clauses. Cats provides utilities that make type classes easier to use, and you will sometimes seem these patterns in other libraries. @@ -97,25 +90,20 @@ The simplest way of creating an interface that uses a type class is to place methods in a singleton object: ```scala mdoc:silent -object Json { +object Json: def toJson[A](value: A)(using w: JsonWriter[A]): Json = w.write(value) -} ``` To use this object, we import any type class instances we care about and call the relevant method: -```scala mdoc:silent -import JsonWriterInstances.personWriter -``` - ```scala mdoc Json.toJson(Person("Dave", "dave@example.com")) ``` The compiler spots that we've called the `toJson` method -without providing the implicit parameters. +without providing the using clauses. It tries to fix this by searching for type class instances of the relevant types and inserting them at the call site: @@ -134,25 +122,20 @@ referred to as "type enrichment" or "pimping". These are older terms that we don't use anymore. ```scala mdoc:silent -extension [A](value: A) { +extension [A](value: A) def toJson(using w: JsonWriter[A]): Json = w.write(value) -} ``` We use interface syntax by importing it alongside the instances for the types we need: -```scala mdoc:silent -import JsonWriterInstances.personWriter -``` - ```scala mdoc Person("Dave", "dave@example.com").toJson ``` Again, the compiler searches for candidates -for the implicit parameters and fills them in for us: +for the using clauses and fills them in for us: ```scala mdoc:silent Person("Dave", "dave@example.com").toJson(using personWriter) @@ -173,8 +156,6 @@ We can use `implicitly` to summon any value from implicit scope. We provide the type we want and `implicitly` does the rest: ```scala mdoc -import JsonWriterInstances.stringWriter - implicitly[JsonWriter[String]] ``` @@ -182,4 +163,4 @@ Most type classes in Cats provide other means to summon instances. However, `implicitly` is a good fallback for debugging purposes. We can insert a call to `implicitly` within the general flow of our code to ensure the compiler can find an instance of a type class -and ensure that there are no ambiguous implicit errors. +and ensure that there are no ambiguous given instances errors. From 5be7e9a32d904c452c43a631dea9be6fd3ab7a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 14:47:56 -0600 Subject: [PATCH 17/33] Scala 3 overhaul to 3.2 --- src/pages/type-classes/implicits.md | 165 ++++++++++------------------ 1 file changed, 60 insertions(+), 105 deletions(-) diff --git a/src/pages/type-classes/implicits.md b/src/pages/type-classes/implicits.md index 09bdba73..e2e53d63 100644 --- a/src/pages/type-classes/implicits.md +++ b/src/pages/type-classes/implicits.md @@ -1,65 +1,41 @@ -## Working with Implicits +## Working with Given Instances and Using Clauses ```scala mdoc:invisible // Forward definitions -enum Json { +enum Json: case JsObject(get: Map[String, Json]) case JsString(get: String) case JsNumber(get: Double) case JsNull -} -import Json.* - -trait JsonWriter[A] { +trait JsonWriter[A]: def write(value: A): Json -} case class Person(name: String, email: String) -object JsonWriterInstances { - given stringWriter: JsonWriter[String] with - def write(value: String): Json = - JsString(value) - - given personWriter: JsonWriter[Person] with - def write(value: Person): Json = - JsObject(Map( - "name" -> JsString(value.name), - "email" -> JsString(value.email) - )) +given stringWriter: JsonWriter[String] with + def write(value: String): Json = + Json.JsString(value) - // etc... -} +given personWriter: JsonWriter[Person] with + def write(value: Person): Json = + Json.JsObject(Map( + "name" -> Json.JsString(value.name), + "email" -> Json.JsString(value.email) + )) -import JsonWriterInstances.{personWriter, stringWriter} +// etc... -object Json { +object Json: def toJson[A](value: A)(using w: JsonWriter[A]): Json = w.write(value) -} ``` Working with type classes in Scala means -working with implicit values and implicit parameters. +working with given instances and using clauses. There are a few rules we need to know to do this effectively. - -### Packaging Implicits - -In a curious quirk of the language, -any definitions marked `implicit` in Scala must be placed -inside an object or trait rather than at the top level. -In the example above we packaged our type class instances -in an object called `JsonWriterInstances`. -We could equally have placed them -in a companion object to `JsonWriter`. -Placing instances in a companion object -to the type class has special significance in Scala -because it plays into something called *implicit scope*. - - ### Implicit Scope As we saw above, the compiler searches @@ -75,7 +51,7 @@ Json.toJson("A string!") The places where the compiler searches for candidate instances is known as the *implicit scope*. The implicit scope applies at the call site; -that is the point where we call a method with an implicit parameter. +that is the point where we call a method with a using clause. The implicit scope which roughly consists of: - local or inherited definitions; @@ -86,61 +62,50 @@ The implicit scope which roughly consists of: of the type class or the parameter type (in this case `JsonWriter` or `String`). -Definitions are only included in implicit scope -if they are tagged with the `implicit` keyword. Furthermore, if the compiler sees multiple candidate definitions, -it fails with an *ambiguous implicit values* error: +it fails with an *ambiguous given instances* error: ```scala mdoc:invisible:reset-object -enum Json { +enum Json: case JsObject(get: Map[String, Json]) case JsString(get: String) case JsNumber(get: Double) case JsNull -} - -import Json.* -trait JsonWriter[A] { +trait JsonWriter[A]: def write(value: A): Json -} -object JsonWriterInstances { - given stringWriter: JsonWriter[String] with - def write(value: String): Json = - JsString(value) -} +given stringWriter: JsonWriter[String] with + def write(value: String): Json = + Json.JsString(value) -object Json { +object Json: def toJson[A](value: A)(using w: JsonWriter[A]): Json = w.write(value) -} ``` ```scala mdoc:fail -given writer1: JsonWriter[String] = - JsonWriterInstances.stringWriter - -given writer2: JsonWriter[String] = +given secondStringWriter: JsonWriter[String] = JsonWriterInstances.stringWriter Json.toJson("A string") ``` -The precise rules of implicit resolution are more complex than this, +The precise rules of given instance resolution are more complex than this, but the complexity is largely irrelevant for day-to-day use[^implicit-search]. -For our purposes, we can package type class instances in roughly four ways: +For our purposes, we can package type class instances in roughly five ways: -1. by placing them in an object such as `JsonWriterInstances`; -2. by placing them in a trait; -3. by placing them in the companion object of the type class; -4. by placing them in the companion object of the parameter type. +1. by placing them as top level definitions in a package. +2. by placing them in an object such as `JsonWriterInstances`; +3. by placing them in a trait; +4. by placing them in the companion object of the type class; +5. by placing them in the companion object of the parameter type. -With option 1 we bring instances into scope by `importing` them. -With option 2 we bring them into scope with inheritance. -With options 3 and 4 instances are *always* in implicit scope, +With option 1 and 2 we bring given instances into scope by `importing` them explicitly. +With option 3 we bring them into scope with inheritance. +With options 4 and 5 instances are *always* in implicit scope, regardless of where we try to use them. -It is conventional to put type class instances in a companion object (option 3 and 4 above) +It is conventional to put type class instances in a companion object (option 4 and 5 above) if there is only one sensible implementation, or at least one implementation that is widely accepted as the default. This makes type class instances easier to use @@ -151,32 +116,30 @@ start by taking a look at [this Stack Overflow post on implicit scope][link-so-i and [this blog post on implicit priority][link-implicit-priority]. -### Recursive Implicit Resolution {#sec:type-classes:recursive-implicits} +### Recursive Given Instance Resolution {#sec:type-classes:recursive-implicits} -The power of type classes and implicits lies in -the compiler's ability to *combine* implicit definitions +The power of type classes with given instances and using clauses lies in +the compiler's ability to *combine* given instances definitions when searching for candidate instances. This is sometimes known as *type class composition*. Earlier we insinuated that all type class instances -are `implicit vals`. This was a simplification. +are `given`. This was a simplification. We can actually define instances in two ways: 1. by defining concrete instances as - `implicit vals` of the required type[^implicit-objects]; + `given` of the required type; -2. by defining `implicit` methods to +2. by defining `given` methods to construct instances from other type class instances. -[^implicit-objects]: We can also use an `implicit object`, which provides the same thing as an `implicit val`. - Why would we construct instances from other instances? As a motivational example, consider defining a `JsonWriter` for `Option`. We would need a `JsonWriter[Option[A]]` for every `A` we care about in our application. We could try to brute force the problem by creating -a library of `implicit vals`: +a library of `given`s: ```scala given optionIntWriter: JsonWriter[Option[Int]] with @@ -189,7 +152,7 @@ given optionPersonWriter: JsonWriter[Option[Person]] with ``` However, this approach clearly doesn't scale. -We end up requiring two `implicit vals` +We end up requiring two `given` instances for every type `A` in our application: one for `A` and one for `Option[A]`. @@ -201,28 +164,22 @@ into a common constructor based on the instance for `A`: - if the option is `None`, return `JsNull`. -Here is the same code written out as an `implicit def`: +Here is the same code written out as a `given` with a `using` clause: ```scala mdoc:silent -implicit def optionWriter[A] - (using writer: JsonWriter[A]): JsonWriter[Option[A]] = - new JsonWriter[Option[A]] { - def write(option: Option[A]): Json = - option match { - case Some(aValue) => writer.write(aValue) - case None => JsNull - } - } +given optionWriter[A](using writer: JsonWriter[A]): JsonWriter[Option[A]] with + def write(option: Option[A]): Json = + option match { + case Some(aValue) => writer.write(aValue) + case None => Json.JsNull + } ``` This method *constructs* a `JsonWriter` for `Option[A]` by -relying on an implicit parameter to +relying on a using clause to fill in the `A`-specific functionality. When the compiler sees an expression like this: -```scala mdoc:invisible -import JsonWriterInstances.stringWriter -``` ```scala mdoc:silent Json.toJson(Option("A string")) ``` @@ -241,9 +198,9 @@ to use as the parameter to `optionWriter`: Json.toJson(Option("A string"))(using optionWriter(using stringWriter)) ``` -In this way, implicit resolution becomes +In this way, given instance resolution becomes a search through the space of possible combinations -of implicit definitions, to find +of given definitions, to find a combination that creates a type class instance of the correct overall type. @@ -251,13 +208,13 @@ of the correct overall type. *Implicit Conversions* When you create a type class instance constructor -using an `implicit def`, +using an `given`, be sure to mark the parameters to the method -as `implicit` parameters. +as `using` parameters. Without this keyword, the compiler won't be able to -fill in the parameters during implicit resolution. +fill in the parameters during given instance resolution. -`implicit` methods with non-`implicit` parameters +`given` methods with non-`using` parameters form a different Scala pattern called an *implicit conversion*. This is also different from the previous section on `Interface Syntax`, because in that case the `JsonWriter` is an implicit class with extension methods. @@ -269,13 +226,11 @@ by importing `scala.language.implicitConversions` in your file: ```scala mdoc:invisible:reset type Json = Nothing -trait JsonWriter[A] { +trait JsonWriter[A]: def write(value: A): Json -} ``` -```scala -implicit def optionWriter[A] - (writer: JsonWriter[A]): JsonWriter[Option[A]] = +```scala modc:warn +given optionWriter[A](writer: JsonWriter[A]): JsonWriter[Option[A]] = ??? // warning: implicit conversion method foo should be enabled // by making the implicit value scala.language.implicitConversions visible. From d69e5a4086babbb9e1db6fdd612445c40ee9b582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 15:46:06 -0600 Subject: [PATCH 18/33] Scala 3 overhaul to 3.3 --- src/pages/type-classes/printable.md | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/pages/type-classes/printable.md b/src/pages/type-classes/printable.md index 077fc023..06e0b8f1 100644 --- a/src/pages/type-classes/printable.md +++ b/src/pages/type-classes/printable.md @@ -12,8 +12,7 @@ Let's define a `Printable` type class to work around these problems: 1. Define a type class `Printable[A]` containing a single method `format`. `format` should accept a value of type `A` and return a `String`. - 2. Create an object `PrintableInstances` - containing instances of `Printable` for `String` and `Int`. + 2. Create instances of `Printable` for `String` and `Int`. 3. Define an object `Printable` with two generic interface methods: @@ -29,34 +28,30 @@ These steps define the three main components of our type class. First we define `Printable`---the *type class* itself: ```scala mdoc:silent:reset-object -trait Printable[A] { +trait Printable[A]: def format(value: A): String -} ``` Then we define some default *instances* of `Printable` and package them in `PrintableInstances`: ```scala mdoc:silent -object PrintableInstances { - given stringPrintable: Printable[String] with - def format(input: String) = input +given stringPrintable: Printable[String] with + def format(input: String) = input - given intPrintable: Printable[Int] with - def format(input: Int) = input.toString -} +given intPrintable: Printable[Int] with + def format(input: Int) = input.toString ``` Finally we define an *interface* object, `Printable`: ```scala mdoc:silent -object Printable { +object Printable: def format[A](input: A)(using p: Printable[A]): String = p.format(input) def print[A](input: A)(using p: Printable[A]): Unit = println(p.format(input)) -} ```
@@ -102,8 +97,6 @@ These either go into the companion object of `Cat` or a separate object to act as a namespace: ```scala mdoc:silent -import PrintableInstances.{intPrintable, stringPrintable} - given catPrintable: Printable[Cat] with def format(cat: Cat) = { val name = Printable.format(cat.name) @@ -149,13 +142,12 @@ by defining some extension methods to provide better syntax: First we define our extension methods: ```scala mdoc:silent -extension [A](value: A) { +extension [A](value: A) def format(using p: Printable[A]): String = p.format(value) def print(using p: Printable[A]): Unit = println(format(using p)) -} ``` With the extensions in scope, From 0308dc65355a2d7eba26a3ad63cf2b6369f08db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 16:03:32 -0600 Subject: [PATCH 19/33] Scala 3 overhaul to 3.4 --- src/pages/type-classes/cats.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/type-classes/cats.md b/src/pages/type-classes/cats.md index 5af9b393..8ff7c232 100644 --- a/src/pages/type-classes/cats.md +++ b/src/pages/type-classes/cats.md @@ -17,9 +17,8 @@ Here's an abbreviated definition: ```scala package cats -trait Show[A] { +trait Show[A]: def show(value: A): String -} ``` ### Importing Type Classes @@ -137,7 +136,7 @@ There are two construction methods on the companion object of `Show` that we can use to define instances for our own types: ```scala -object Show { +object Show: // Convert a function to a `Show` instance: def show[A](f: A => String): Show[A] = ??? @@ -145,7 +144,6 @@ object Show { // Create a `Show` instance from a `toString` method: def fromToString[A]: Show[A] = ??? -} ``` These allow us to quickly construct instances From 24e9f338d26a5cc947aaf26a4916615c485d708b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 16:03:48 -0600 Subject: [PATCH 20/33] Scala 3 overhaul to 3.5 --- src/pages/type-classes/equal.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/type-classes/equal.md b/src/pages/type-classes/equal.md index f9343147..7c8059a1 100644 --- a/src/pages/type-classes/equal.md +++ b/src/pages/type-classes/equal.md @@ -34,10 +34,9 @@ between instances of any given type: ```scala package cats -trait Eq[A] { +trait Eq[A]: def eqv(a: A, b: A): Boolean // other concrete methods based on eqv... -} ``` The interface syntax, defined in [`cats.syntax.eq`][cats.syntax.eq], From 31c2f7c33c698b8a1278329bf17ff3204c2cd022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 16:04:03 -0600 Subject: [PATCH 21/33] Scala 3 overhaul to 3.6 --- src/pages/type-classes/instance-selection.md | 51 +++++++++----------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/pages/type-classes/instance-selection.md b/src/pages/type-classes/instance-selection.md index 16a3bf7a..b4e0e592 100644 --- a/src/pages/type-classes/instance-selection.md +++ b/src/pages/type-classes/instance-selection.md @@ -23,8 +23,8 @@ that control instance selection: When we define type classes we can add variance annotations to the type parameter to affect the variance of the type class -and the compiler's ability to select instances -during implicit resolution. +and the compiler's ability to select given instances +during resolution. To recap Essential Scala, variance relates to subtypes. @@ -59,20 +59,17 @@ anywhere we expect a `List[Shape]` because `Circle` is a subtype of `Shape`: ```scala mdoc:silent -enum Shape { +enum Shape: case Circle(radius: Double) -} - -import Shape.Circle ``` ```scala -val circles: List[Circle] = ??? +val circles: List[Shape.Circle] = ??? val shapes: List[Shape] = circles ``` ```scala mdoc:invisible -val circles: List[Circle] = null +val circles: List[Shape.Circle] = null val shapes: List[Shape] = circles ``` @@ -101,32 +98,31 @@ trait Json ``` ```scala mdoc -trait JsonWriter[-A] { +trait JsonWriter[-A]: def write(value: A): Json -} ``` Let's unpack this a bit further. Remember that variance is all about the ability to substitute one value for another. Consider a scenario where we have two values, -one of type `Shape` and one of type `Circle`, -and two `JsonWriters`, one for `Shape` and one for `Circle`: +one of type `Shape` and one of type `Shape.Circle`, +and two `JsonWriters`, one for `Shape` and one for `Shape.Circle`: ```scala val shape: Shape = ??? -val circle: Circle = ??? +val circle: Shape.Circle = ??? val shapeWriter: JsonWriter[Shape] = ??? -val circleWriter: JsonWriter[Circle] = ??? +val circleWriter: JsonWriter[Shape.Circle] = ??? ``` ```scala mdoc:invisible val shape: Shape = null -val circle: Circle = null +val circle: Shape.Circle = null val shapeWriter: JsonWriter[Shape] = null -val circleWriter: JsonWriter[Circle] = null +val circleWriter: JsonWriter[Shape.Circle] = null ``` ```scala mdoc:silent @@ -136,16 +132,16 @@ def format[A](value: A, writer: JsonWriter[A]): Json = Now ask yourself the question: "Which combinations of value and writer can I pass to `format`?" -We can `write` a `Circle` with either writer +We can `write` a `Shape.Circle` with either writer because all `Circles` are `Shapes`. Conversely, we can't write a `Shape` with `circleWriter` because not all `Shapes` are `Circles`. This relationship is what we formally model using contravariance. -`JsonWriter[Shape]` is a subtype of `JsonWriter[Circle]` -because `Circle` is a subtype of `Shape`. +`JsonWriter[Shape]` is a subtype of `JsonWriter[Shape.Circle]` +because `Shape.Circle` is a subtype of `Shape`. This means we can use `shapeWriter` -anywhere we expect to see a `JsonWriter[Circle]`. +anywhere we expect to see a `JsonWriter[Shape.Circle]`. **Invariance** @@ -163,7 +159,7 @@ are never subtypes of one another, no matter what the relationship between `A` and `B`. This is the default semantics for Scala type constructors. -When the compiler searches for an implicit +When the compiler searches for a given instance it looks for one matching the type *or subtype*. Thus we can use variance annotations to control type class instance selection to some extent. @@ -172,9 +168,8 @@ There are two issues that tend to arise. Let's imagine we have an algebraic data type like: ```scala -sealed trait A -final case object B extends A -final case object C extends A +enum A: + case B, C ``` The issues are: @@ -182,13 +177,13 @@ The issues are: 1. Will an instance defined on a supertype be selected if one is available? For example, can we define an instance for `A` - and have it work for values of type `B` and `C`? + and have it work for values of type `A.B` and `A.C`? 2. Will an instance for a subtype be selected in preference to that of a supertype. - For instance, if we define an instance for `A` and `B`, - and we have a value of type `B`, - will the instance for `B` be selected in preference to `A`? + For instance, if we define an instance for `A` and `A.B`, + and we have a value of type `A.B`, + will the instance for `A.B` be selected in preference to `A`? It turns out we can't have both at once. The three choices give us behaviour as follows: From ca04554d813c1779601fee4df61e01f04f94c190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 19:58:19 -0600 Subject: [PATCH 22/33] Scala 3 overhaul to 4.x --- src/pages/monoids/index.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/pages/monoids/index.md b/src/pages/monoids/index.md index f78ae45c..e95f3dd6 100644 --- a/src/pages/monoids/index.md +++ b/src/pages/monoids/index.md @@ -100,10 +100,9 @@ This definition translates nicely into Scala code. Here is a simplified version of the definition from Cats: ```scala mdoc:silent -trait Monoid[A] { +trait Monoid[A]: def combine(x: A, y: A): A def empty: A -} ``` In addition to providing the `combine` and `empty` operations, @@ -161,13 +160,11 @@ A more accurate (though still simplified) definition of Cats' [`Monoid`][cats.Monoid] is: ```scala mdoc:silent:reset-object -trait Semigroup[A] { +trait Semigroup[A]: def combine(x: A, y: A): A -} -trait Monoid[A] extends Semigroup[A] { +trait Monoid[A] extends Semigroup[A]: def empty: A -} ``` We'll see this kind of inheritance often when discussing type classes. @@ -185,18 +182,15 @@ and convince yourself that the monoid laws hold. Use the following definitions as a starting point: ```scala mdoc:reset:silent -trait Semigroup[A] { +trait Semigroup[A]: def combine(x: A, y: A): A -} -trait Monoid[A] extends Semigroup[A] { +trait Monoid[A] extends Semigroup[A]: def empty: A -} -object Monoid { +object Monoid: def apply[A](using monoid: Monoid[A]) = monoid -} ```
From dd793d034fe0793f7cd10b392bbe5b5719b1ae5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 20:07:45 -0600 Subject: [PATCH 23/33] =?UTF-8?q?coffeescript:=202.5.1=20=E2=86=92=202.7.0?= =?UTF-8?q?=20via=20yarn=20upgradeInteractive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +++--- yarn.lock | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4287edb5..42517805 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,12 @@ "devDependencies": { "bootstrap": "^3.4.1", "coffeeify": "1.0.0", + "coffeescript": "^2.5.1", "jquery": "3.5.0", - "uglifyify": "2.6.0", "lessc": "^1.0.2", - "underscore": "1.7.0", "pandoc-filter": "0.1.6", - "coffeescript": "^2.5.1" + "uglifyify": "2.6.0", + "underscore": "1.7.0" }, "author": "Noel Welsh and Dave Gurnell" } diff --git a/yarn.lock b/yarn.lock index 01eba297..a30fc0d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -96,8 +96,8 @@ coffeeify@1.0.0: through "^2.3.6" coffeescript@^2.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz" + version "2.7.0" + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-2.7.0.tgz" combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" From e9a9fe068dd100e002d37e5b7e28d760c61387ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 20:09:06 -0600 Subject: [PATCH 24/33] =?UTF-8?q?import=20`.=5F`=20=E2=86=92=20`.*`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/applicatives/examples.md | 26 ++++----- src/pages/applicatives/index.md | 2 +- src/pages/applicatives/parallel.md | 12 ++--- src/pages/applicatives/semigroupal.md | 20 +++---- src/pages/applicatives/validated.md | 26 ++++----- src/pages/case-studies/crdt/abstraction.md | 10 ++-- src/pages/case-studies/crdt/generalisation.md | 10 ++-- src/pages/case-studies/map-reduce/index.md | 54 +++++++++---------- src/pages/case-studies/parser/applicative.md | 8 +-- src/pages/case-studies/testing/index.md | 24 ++++----- src/pages/case-studies/validation/check.md | 32 +++++------ src/pages/case-studies/validation/kleisli.md | 10 ++-- src/pages/case-studies/validation/map.md | 36 ++++++------- src/pages/foldable-traverse/foldable-cats.md | 20 +++---- src/pages/foldable-traverse/foldable.md | 2 +- src/pages/foldable-traverse/traverse-cats.md | 10 ++-- src/pages/foldable-traverse/traverse.md | 24 ++++----- src/pages/functors/cats.md | 12 ++--- .../functors/contravariant-invariant-cats.md | 10 ++-- src/pages/functors/index.md | 6 +-- src/pages/functors/partial-unification.md | 6 +-- src/pages/monad-transformers/index.md | 30 +++++------ src/pages/monads/cats.md | 32 +++++------ src/pages/monads/custom-instances.md | 12 ++--- src/pages/monads/either.md | 8 +-- src/pages/monads/id.md | 12 ++--- src/pages/monads/monad-error.md | 14 ++--- src/pages/monads/reader.md | 2 +- src/pages/monads/state.md | 4 +- src/pages/monads/writer.md | 24 ++++----- src/pages/monoids/cats.md | 32 +++++------ src/pages/monoids/summary.md | 12 ++--- src/pages/type-classes/cats.md | 16 +++--- src/pages/type-classes/equal.md | 20 +++---- 34 files changed, 289 insertions(+), 289 deletions(-) diff --git a/src/pages/applicatives/examples.md b/src/pages/applicatives/examples.md index 60c22450..b85bc63f 100644 --- a/src/pages/applicatives/examples.md +++ b/src/pages/applicatives/examples.md @@ -12,9 +12,9 @@ provide parallel as opposed to sequential execution: ```scala mdoc:silent import cats.Semigroupal -import cats.instances.future._ // for Semigroupal -import scala.concurrent._ -import scala.concurrent.duration._ +import cats.instances.future.* // for Semigroupal +import scala.concurrent.* +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global val futurePair = Semigroupal[Future]. @@ -31,7 +31,7 @@ by the time we call `product`. We can use apply syntax to zip fixed numbers of `Futures`: ```scala mdoc:silent -import cats.syntax.apply._ // for mapN +import cats.syntax.apply.* // for mapN case class Cat( name: String, @@ -59,7 +59,7 @@ but we actually get the cartesian product of their elements: ```scala mdoc:silent import cats.Semigroupal -import cats.instances.list._ // for Semigroupal +import cats.instances.list.* // for Semigroupal ``` ```scala mdoc @@ -81,7 +81,7 @@ we find that `product` implements the same fail-fast behaviour as `flatMap`: ```scala mdoc:silent -import cats.instances.either._ // for Semigroupal +import cats.instances.either.* // for Semigroupal type ErrorOr[A] = Either[Vector[String], A] ``` @@ -105,8 +105,8 @@ If we have a monad we can implement `product` as follows. ```scala mdoc:silent import cats.Monad -import cats.syntax.functor._ // for map -import cats.syntax.flatMap._ // for flatmap +import cats.syntax.functor.* // for map +import cats.syntax.flatMap.* // for flatmap def product[F[_]: Monad, A, B](fa: F[A], fb: F[B]): F[(A,B)] = fa.flatMap(a => @@ -178,8 +178,8 @@ the definition of `product` in terms of import cats.Monad ``` ```scala mdoc:silent -import cats.syntax.functor._ // for map -import cats.syntax.flatMap._ // for flatMap +import cats.syntax.functor.* // for map +import cats.syntax.flatMap.* // for flatMap def product[F[_]: Monad, A, B](x: F[A], y: F[B]): F[(A, B)] = x.flatMap(a => y.map(b => (a, b))) @@ -189,8 +189,8 @@ This code is equivalent to a for comprehension: ```scala mdoc:invisible:reset-object import cats.Monad -import cats.syntax.flatMap._ // for flatMap -import cats.syntax.functor._ // for map +import cats.syntax.flatMap.* // for flatMap +import cats.syntax.functor.* // for map ``` ```scala mdoc:silent def product[F[_]: Monad, A, B](x: F[A], y: F[B]): F[(A, B)] = @@ -204,7 +204,7 @@ The semantics of `flatMap` are what give rise to the behaviour for `List` and `Either`: ```scala mdoc:silent -import cats.instances.list._ // for Semigroupal +import cats.instances.list.* // for Semigroupal ``` ```scala mdoc diff --git a/src/pages/applicatives/index.md b/src/pages/applicatives/index.md index cb45b25a..70211cee 100644 --- a/src/pages/applicatives/index.md +++ b/src/pages/applicatives/index.md @@ -19,7 +19,7 @@ fails on the first call to `parseInt` and doesn't go any further: ```scala mdoc:silent -import cats.syntax.either._ // for catchOnly +import cats.syntax.either.* // for catchOnly def parseInt(str: String): Either[String, Int] = Either.catchOnly[NumberFormatException](str.toInt). diff --git a/src/pages/applicatives/parallel.md b/src/pages/applicatives/parallel.md index 8089891f..912437e8 100644 --- a/src/pages/applicatives/parallel.md +++ b/src/pages/applicatives/parallel.md @@ -17,7 +17,7 @@ stops at the first error. ```scala mdoc:silent import cats.Semigroupal -import cats.instances.either._ // for Semigroupal +import cats.instances.either.* // for Semigroupal type ErrorOr[A] = Either[Vector[String], A] val error1: ErrorOr[Int] = Left(Vector("Error 1")) @@ -33,8 +33,8 @@ using `tupled` as a short-cut. ```scala mdoc:silent -import cats.syntax.apply._ // for tupled -import cats.instances.vector._ // for Semigroup on Vector +import cats.syntax.apply.* // for tupled +import cats.instances.vector.* // for Semigroup on Vector ``` ```scala mdoc (error1, error2).tupled @@ -45,7 +45,7 @@ we simply replace `tupled` with its "parallel" version called `parTupled`. ```scala mdoc:silent -import cats.syntax.parallel._ // for parTupled +import cats.syntax.parallel.* // for parTupled ``` ```scala mdoc (error1, error2).parTupled @@ -57,7 +57,7 @@ Any type that has a `Semigroup` instance will work. For example, here we use `List` instead. ```scala mdoc:silent -import cats.instances.list._ // for Semigroup on List +import cats.instances.list.* // for Semigroup on List type ErrorOrList[A] = Either[List[String], A] val errStr1: ErrorOrList[Int] = Left(List("error 1")) @@ -160,7 +160,7 @@ insted of creating the cartesian product. We can see by writing a little bit of code. ```scala mdoc:silent -import cats.instances.list._ +import cats.instances.list.* ``` ```scala mdoc (List(1, 2), List(3, 4)).tupled diff --git a/src/pages/applicatives/semigroupal.md b/src/pages/applicatives/semigroupal.md index daca552a..b6762ea3 100644 --- a/src/pages/applicatives/semigroupal.md +++ b/src/pages/applicatives/semigroupal.md @@ -34,7 +34,7 @@ Let's join some `Options` as an example: ```scala mdoc:silent:reset-object import cats.Semigroupal -import cats.instances.option._ // for Semigroupal +import cats.instances.option.* // for Semigroupal ``` ```scala mdoc @@ -59,7 +59,7 @@ For example, the methods `tuple2` through `tuple22` generalise `product` to different arities: ```scala mdoc:silent -import cats.instances.option._ // for Semigroupal +import cats.instances.option.* // for Semigroupal ``` ```scala mdoc @@ -98,8 +98,8 @@ We import the syntax from [`cats.syntax.apply`][cats.syntax.apply]. Here's an example: ```scala mdoc:silent -import cats.instances.option._ // for Semigroupal -import cats.syntax.apply._ // for tupled and mapN +import cats.instances.option.* // for Semigroupal +import cats.syntax.apply.* // for tupled and mapN ``` The `tupled` method is implicitly added to the tuple of `Options`. @@ -167,11 +167,11 @@ Here's an example: ```scala mdoc:silent:reset-object import cats.Monoid -import cats.instances.int._ // for Monoid -import cats.instances.invariant._ // for Semigroupal -import cats.instances.list._ // for Monoid -import cats.instances.string._ // for Monoid -import cats.syntax.apply._ // for imapN +import cats.instances.int.* // for Monoid +import cats.instances.invariant.* // for Semigroupal +import cats.instances.list.* // for Monoid +import cats.instances.string.* // for Monoid +import cats.syntax.apply.* // for imapN final case class Cat( name: String, @@ -196,7 +196,7 @@ Our `Monoid` allows us to create "empty" `Cats`, and add `Cats` together using the syntax from Chapter [@sec:monoids]: ```scala mdoc:silent -import cats.syntax.semigroup._ // for |+| +import cats.syntax.semigroup.* // for |+| val garfield = Cat("Garfield", 1978, List("Lasagne")) val heathcliff = Cat("Heathcliff", 1988, List("Junk Food")) diff --git a/src/pages/applicatives/validated.md b/src/pages/applicatives/validated.md index 97f917af..478902bd 100644 --- a/src/pages/applicatives/validated.md +++ b/src/pages/applicatives/validated.md @@ -21,7 +21,7 @@ is therefore free to accumulate errors: ```scala mdoc:silent import cats.Semigroupal import cats.data.Validated -import cats.instances.list._ // for Monoid +import cats.instances.list.* // for Monoid type AllErrorsOr[A] = Validated[List[String], A] ``` @@ -59,7 +59,7 @@ which widen the return type to `Validated`: ```scala mdoc:invisible:reset-object import cats.data.Validated -import cats.instances.list._ // for Monoid +import cats.instances.list.* // for Monoid type AllErrorsOr[A] = Validated[List[String], A] ``` @@ -73,7 +73,7 @@ the `valid` and `invalid` extension methods from `cats.syntax.validated`: ```scala mdoc:silent -import cats.syntax.validated._ // for valid and invalid +import cats.syntax.validated.* // for valid and invalid ``` ```scala mdoc @@ -87,8 +87,8 @@ and [`cats.syntax.applicativeError`][cats.syntax.applicativeError] respectively: ```scala mdoc:silent -import cats.syntax.applicative._ // for pure -import cats.syntax.applicativeError._ // for raiseError +import cats.syntax.applicative.* // for pure +import cats.syntax.applicativeError.* // for raiseError type ErrorsOr[A] = Validated[List[String], A] ``` @@ -130,7 +130,7 @@ number of parameters for `Semigroupal`: ```scala mdoc:invisible:reset-object import cats.data.Validated import cats.Semigroupal -import cats.syntax.validated._ +import cats.syntax.validated.* ``` ```scala mdoc:silent type AllErrorsOr[A] = Validated[String, A] @@ -149,7 +149,7 @@ Once we import a `Semigroup` for the error type, everything works as expected: ```scala mdoc:silent -import cats.instances.string._ // for Semigroup +import cats.instances.string.* // for Semigroup ``` ```scala mdoc @@ -163,7 +163,7 @@ or any of the other `Semigroupal` methods to accumulate errors as we like: ```scala mdoc:silent -import cats.syntax.apply._ // for tupled +import cats.syntax.apply.* // for tupled ``` ```scala mdoc @@ -178,7 +178,7 @@ for accumulating errors. We commonly use `Lists` or `Vectors` instead: ```scala mdoc:silent -import cats.instances.vector._ // for Semigroupal +import cats.instances.vector.* // for Semigroupal ``` ```scala mdoc @@ -246,7 +246,7 @@ using the `toEither` and `toValidated` methods. Note that `toValidated` comes from [`cats.syntax.either`]: ```scala mdoc -import cats.syntax.either._ // for toValidated +import cats.syntax.either.* // for toValidated "Badness".invalid[Int] "Badness".invalid[Int].toEither @@ -371,7 +371,7 @@ and we use `leftMap` to turn it into an error message: ```scala mdoc:silent -import cats.syntax.either._ // for catchOnly +import cats.syntax.either.* // for catchOnly type NumFmtExn = NumberFormatException @@ -468,8 +468,8 @@ We can do this by switching from `Either` to `Validated` and using apply syntax: ```scala mdoc:silent -import cats.instances.list._ // for Semigroupal -import cats.syntax.apply._ // for mapN +import cats.instances.list.* // for Semigroupal +import cats.syntax.apply.* // for mapN def readUser(data: FormData): FailSlow[User] = ( diff --git a/src/pages/case-studies/crdt/abstraction.md b/src/pages/case-studies/crdt/abstraction.md index 5fd1c71a..b7dc8dda 100644 --- a/src/pages/case-studies/crdt/abstraction.md +++ b/src/pages/case-studies/crdt/abstraction.md @@ -79,10 +79,10 @@ in the companion object for `GCounter` to place it in global implicit scope: ```scala mdoc:silent -import cats.instances.list._ // for Monoid -import cats.instances.map._ // for Monoid -import cats.syntax.semigroup._ // for |+| -import cats.syntax.foldable._ // for combineAll +import cats.instances.list.* // for Monoid +import cats.instances.map.* // for Monoid +import cats.syntax.semigroup.* // for |+| +import cats.syntax.foldable.* // for combineAll given mapGCounterInstance[K, V]: GCounter[Map, K, V] with def increment(map: Map[K, V])(key: K, value: V) @@ -104,7 +104,7 @@ given mapGCounterInstance[K, V]: GCounter[Map, K, V] with You should be able to use your instance as follows: ```scala mdoc:silent -import cats.instances.int._ // for Monoid +import cats.instances.int.* // for Monoid val g1 = Map("a" -> 7, "b" -> 3) val g2 = Map("a" -> 2, "b" -> 5) diff --git a/src/pages/case-studies/crdt/generalisation.md b/src/pages/case-studies/crdt/generalisation.md index 6dd60bc8..727550fa 100644 --- a/src/pages/case-studies/crdt/generalisation.md +++ b/src/pages/case-studies/crdt/generalisation.md @@ -167,7 +167,7 @@ object wrapper { val empty: Set[A] = Set.empty[A] } -}; import wrapper._ +}; import wrapper.* ```
@@ -192,10 +192,10 @@ which significantly simplifies the process of merging and maximising counters: ```scala mdoc:silent -import cats.instances.list._ // for Monoid -import cats.instances.map._ // for Monoid -import cats.syntax.semigroup._ // for |+| -import cats.syntax.foldable._ // for combineAll +import cats.instances.list.* // for Monoid +import cats.instances.map.* // for Monoid +import cats.syntax.semigroup.* // for |+| +import cats.syntax.foldable.* // for combineAll final case class GCounter[A](counters: Map[String,A]) { def increment(machine: String, amount: A) diff --git a/src/pages/case-studies/map-reduce/index.md b/src/pages/case-studies/map-reduce/index.md index 8606c179..2d00d093 100644 --- a/src/pages/case-studies/map-reduce/index.md +++ b/src/pages/case-studies/map-reduce/index.md @@ -161,14 +161,14 @@ Here's some sample output for reference: ```scala mdoc:invisible:reset import cats.Monoid -import cats.syntax.semigroup._ // for |+| +import cats.syntax.semigroup.* // for |+| def foldMap[A, B: Monoid](values: Vector[A])(func: A => B): B = values.foldLeft(Monoid[B].empty)(_ |+| func(_)) ``` ```scala mdoc:silent -import cats.instances.int._ // for Monoid +import cats.instances.int.* // for Monoid ``` ```scala mdoc @@ -176,7 +176,7 @@ foldMap(Vector(1, 2, 3))(identity) ``` ```scala mdoc:silent -import cats.instances.string._ // for Monoid +import cats.instances.string.* // for Monoid ``` ```scala mdoc @@ -194,7 +194,7 @@ as described in Section [@sec:monoid-syntax]: ```scala mdoc:reset:silent import cats.Monoid -import cats.syntax.semigroup._ // for |+| +import cats.syntax.semigroup.* // for |+| def foldMap[A, B : Monoid](as: Vector[A])(func: A => B): B = as.map(func).foldLeft(Monoid[B].empty)(_ |+| _) @@ -204,7 +204,7 @@ We can make a slight alteration to this code to do everything in one step: ```scala mdoc:reset:invisible import cats.Monoid -import cats.syntax.semigroup._ +import cats.syntax.semigroup.* ``` ```scala mdoc:silent def foldMap[A, B : Monoid](as: Vector[A])(func: A => B): B = @@ -304,9 +304,9 @@ Future.sequence(List(Future(1), Future(2), Future(3))) or an instance of `Traverse`: ```scala mdoc:silent -import cats.instances.future._ // for Applicative -import cats.instances.list._ // for Traverse -import cats.syntax.traverse._ // for sequence +import cats.instances.future.* // for Applicative +import cats.instances.list.* // for Traverse +import cats.syntax.traverse.* // for sequence ``` ```scala mdoc @@ -318,8 +318,8 @@ Finally, we can use `Await.result` to block on a `Future` until a result is available: ```scala mdoc:silent -import scala.concurrent._ -import scala.concurrent.duration._ +import scala.concurrent.* +import scala.concurrent.duration.* ``` ```scala mdoc @@ -331,8 +331,8 @@ available from `cats.instances.future`: ```scala mdoc:silent import cats.{Monad, Monoid} -import cats.instances.int._ // for Monoid -import cats.instances.future._ // for Monad and Monoid +import cats.instances.int.* // for Monoid +import cats.instances.future.* // for Monad and Monoid Monad[Future].pure(42) @@ -385,10 +385,10 @@ splits out each `map` and `fold` into a separate line of code: ```scala mdoc:invisible:reset -import cats._ -import cats.implicits._ -import scala.concurrent._ -import scala.concurrent.duration._ +import cats.* +import cats.implicits.* +import scala.concurrent.* +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global ``` ```scala mdoc:silent @@ -432,10 +432,10 @@ are actually equivalent to a single call to `foldMap`, shortening the entire algorithm as follows: ```scala mdoc:reset:invisible -import cats._ -import cats.implicits._ -import scala.concurrent._ -import scala.concurrent.duration._ +import cats.* +import cats.implicits.* +import scala.concurrent.* +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global def foldMap[A, B : Monoid](as: Vector[A])(func: A => B): B = as.foldLeft(Monoid[B].empty)(_ |+| func(_)) @@ -482,15 +482,15 @@ We'll restate all of the necessary imports for completeness: ```scala mdoc:silent:reset import cats.Monoid -import cats.instances.int._ // for Monoid -import cats.instances.future._ // for Applicative and Monad -import cats.instances.vector._ // for Foldable and Traverse +import cats.instances.int.* // for Monoid +import cats.instances.future.* // for Applicative and Monad +import cats.instances.vector.* // for Foldable and Traverse -import cats.syntax.foldable._ // for combineAll and foldMap -import cats.syntax.traverse._ // for traverse +import cats.syntax.foldable.* // for combineAll and foldMap +import cats.syntax.traverse.* // for traverse -import scala.concurrent._ -import scala.concurrent.duration._ +import scala.concurrent.* +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global ``` diff --git a/src/pages/case-studies/parser/applicative.md b/src/pages/case-studies/parser/applicative.md index 1b6873f2..76eec5fe 100644 --- a/src/pages/case-studies/parser/applicative.md +++ b/src/pages/case-studies/parser/applicative.md @@ -107,8 +107,8 @@ The method is called `ap` and types `F[A]` that implement it are called **applic The Scalaz library provides an applicative functor type class, and instances for `Option` and many other types. In Scalaz, `ap` is written as `<*>` for consistency with Haskell. Here's an example: ~~~ scala -import scalaz.syntax.applicative._ -import scalaz.std.option._ +import scalaz.syntax.applicative.* +import scalaz.std.option.* val adder = ((x: Int, y: Int, z: Int) => x + y + z).curried // adder: Int => (Int => (Int => Int)) = @@ -198,7 +198,7 @@ Checkout the `parser-applicative` tag to see the full code and tests. Once we have our `Applicative` instance we can take it for a spin: ~~~ scala -import scalaz.syntax.applicative._ +import scalaz.syntax.applicative.* val parser = Parser.string("chicken") <*> ((_: String) => "Tastes like chicken").point[Parser] // parser: underscore.parser.Parser[String] = Parser() @@ -258,7 +258,7 @@ Sometime we do need more than one result, so the problem still remains. In these Here is it in use ~~~ scala -import scalaz.syntax.applicative._ +import scalaz.syntax.applicative.* def taste(taster: String, action: String, flava: String): String = s"$flava tastes like chicken!" // taste: (taster: String, action: String, flava: String)String diff --git a/src/pages/case-studies/testing/index.md b/src/pages/case-studies/testing/index.md index 76b34648..0017831d 100644 --- a/src/pages/case-studies/testing/index.md +++ b/src/pages/case-studies/testing/index.md @@ -24,9 +24,9 @@ We'll also have an `UptimeService` that maintains a list of servers and allows the user to poll them for their total uptime: ```scala mdoc:silent -import cats.instances.future._ // for Applicative -import cats.instances.list._ // for Traverse -import cats.syntax.traverse._ // for traverse +import cats.instances.future.* // for Applicative +import cats.instances.list.* // for Traverse +import cats.syntax.traverse.* // for traverse import scala.concurrent.ExecutionContext.Implicits.global class UptimeService(client: UptimeClient) { @@ -268,8 +268,8 @@ to `UptimeService`. We can write this as an implicit parameter: ```scala mdoc:invisible:reset-object -import cats.syntax.traverse._ // for traverse -import cats.instances.list._ +import cats.syntax.traverse.* // for traverse +import cats.instances.list.* trait UptimeClient[F[_]] { def getUptime(hostname: String): F[Int] @@ -277,7 +277,7 @@ trait UptimeClient[F[_]] { ``` ```scala mdoc:silent import cats.Applicative -import cats.syntax.functor._ // for map +import cats.syntax.functor.* // for map class UptimeService[F[_]](client: UptimeClient[F]) (using a: Applicative[F]) { @@ -291,9 +291,9 @@ or more tersely as a context bound: ```scala mdoc:reset-object:invisible import cats.Applicative -import cats.syntax.functor._ -import cats.syntax.traverse._ -import cats.instances.list._ +import cats.syntax.functor.* +import cats.syntax.traverse.* +import cats.instances.list.* trait UptimeClient[F[_]] { def getUptime(hostname: String): F[Int] @@ -326,9 +326,9 @@ synchronously without worrying about monads or applicatives: ```scala mdoc:invisible:reset-object import cats.{Id, Applicative} -import cats.instances.list._ // for Traverse -import cats.syntax.functor._ // for map -import cats.syntax.traverse._ // for traverse +import cats.instances.list.* // for Traverse +import cats.syntax.functor.* // for map +import cats.syntax.traverse.* // for traverse import scala.concurrent.Future trait UptimeClient[F[_]] { diff --git a/src/pages/case-studies/validation/check.md b/src/pages/case-studies/validation/check.md index c6b64ad6..bce7c712 100644 --- a/src/pages/case-studies/validation/check.md +++ b/src/pages/case-studies/validation/check.md @@ -83,8 +83,8 @@ the `combine` method or its associated `|+|` syntax: ```scala mdoc:silent import cats.Semigroup -import cats.instances.list._ // for Semigroup -import cats.syntax.semigroup._ // for |+| +import cats.instances.list.* // for Semigroup +import cats.syntax.semigroup.* // for |+| val semigroup = Semigroup[List[String]] ``` @@ -131,8 +131,8 @@ we'll call this implementation `CheckF`: ```scala mdoc:silent import cats.Semigroup -import cats.syntax.either._ // for asLeft and asRight -import cats.syntax.semigroup._ // for |+| +import cats.syntax.either.* // for asLeft and asRight +import cats.syntax.semigroup.* // for |+| ``` ```scala mdoc:silent @@ -157,7 +157,7 @@ Let's test the behaviour we get. First we'll setup some checks: ```scala mdoc:silent -import cats.instances.list._ // for Semigroup +import cats.instances.list.* // for Semigroup val a: CheckF[List[String], Int] = CheckF { v => @@ -193,8 +193,8 @@ What happens if we create instances of `CheckF[Nothing, A]`? ```scala mdoc:invisible:reset-object import cats.Semigroup -import cats.syntax.semigroup._ -import cats.syntax.either._ +import cats.syntax.semigroup.* +import cats.syntax.either.* final case class CheckF[E, A](func: A => Either[E, A]) { def apply(a: A): Either[E, A] = func(a) @@ -235,13 +235,13 @@ We'll call this implementation `Check`: ```scala mdoc:invisible:reset-object import cats.Semigroup -import cats.syntax.either._ // for asLeft and asRight -import cats.syntax.semigroup._ // for |+| +import cats.syntax.either.* // for asLeft and asRight +import cats.syntax.semigroup.* // for |+| ``` ```scala mdoc:silent sealed trait Check[E, A] { - import Check._ + import Check.* def and(that: Check[E, A]): Check[E, A] = And(this, that) @@ -332,12 +332,12 @@ Here's the complete implementation: ```scala mdoc:silent:reset-object import cats.Semigroup import cats.data.Validated -import cats.syntax.apply._ // for mapN +import cats.syntax.apply.* // for mapN ``` ```scala mdoc:silent sealed trait Check[E, A] { - import Check._ + import Check.* def and(that: Check[E, A]): Check[E, A] = And(this, that) @@ -375,14 +375,14 @@ is implicit in the semantics of "or". ```scala mdoc:silent:reset-object import cats.Semigroup import cats.data.Validated -import cats.syntax.semigroup._ // for |+| -import cats.syntax.apply._ // for mapN -import cats.data.Validated._ // for Valid and Invalid +import cats.syntax.semigroup.* // for |+| +import cats.syntax.apply.* // for mapN +import cats.data.Validated.* // for Valid and Invalid ``` ```scala mdoc:silent sealed trait Check[E, A] { - import Check._ + import Check.* def and(that: Check[E, A]): Check[E, A] = And(this, that) diff --git a/src/pages/case-studies/validation/kleisli.md b/src/pages/case-studies/validation/kleisli.md index 84618d9d..ac96d3fd 100644 --- a/src/pages/case-studies/validation/kleisli.md +++ b/src/pages/case-studies/validation/kleisli.md @@ -65,7 +65,7 @@ through three steps: ```scala mdoc:silent import cats.data.Kleisli -import cats.instances.list._ // for Monad +import cats.instances.list.* // for Monad ``` These steps each transform an input `Int` @@ -172,13 +172,13 @@ def checkPred[A](pred: Predicate[Errors, A]): Check[A, A] = // Foreword declarations import cats.Semigroup -import cats.syntax.apply._ // for mapN -import cats.syntax.semigroup._ // for |+| +import cats.syntax.apply.* // for mapN +import cats.syntax.semigroup.* // for |+| import cats.data.Validated import cats.data.Validated.{Valid, Invalid} sealed trait Predicate[E, A] { - import Predicate._ + import Predicate.* def and(that: Predicate[E, A]): Predicate[E, A] = And(this, that) @@ -237,7 +237,7 @@ simplifies things, but the process is still complex: ```scala mdoc:silent import cats.data.{Kleisli, NonEmptyList} -import cats.instances.either._ // for Semigroupal +import cats.instances.either.* // for Semigroupal ``` Here is the preamble we suggested in diff --git a/src/pages/case-studies/validation/map.md b/src/pages/case-studies/validation/map.md index 47a70d50..15211517 100644 --- a/src/pages/case-studies/validation/map.md +++ b/src/pages/case-studies/validation/map.md @@ -89,9 +89,9 @@ Making this change gives us the following code: ```scala mdoc:silent import cats.Semigroup import cats.data.Validated -import cats.syntax.semigroup._ // for |+| -import cats.syntax.apply._ // for mapN -import cats.data.Validated._ // for Valid and Invalid +import cats.syntax.semigroup.* // for |+| +import cats.syntax.apply.* // for mapN +import cats.data.Validated.* // for Valid and Invalid ``` ```scala mdoc:silent @@ -158,11 +158,11 @@ you should be able to create code similar to the below: ```scala mdoc:invisible:reset-object import cats.Semigroup import cats.data.Validated -import cats.implicits._ +import cats.implicits.* sealed trait Predicate[E, A] { - import Predicate._ - import Validated._ + import Predicate.* + import Validated.* def and(that: Predicate[E, A]): Predicate[E, A] = And(this, that) @@ -209,7 +209,7 @@ import cats.data.Validated ```scala mdoc:silent sealed trait Check[E, A, B] { - import Check._ + import Check.* def apply(in: A)(using s: Semigroup[E]): Validated[E, B] @@ -373,10 +373,10 @@ including some tidying and repackaging of the code: ```scala mdoc:silent:reset-object import cats.Semigroup import cats.data.Validated -import cats.data.Validated._ // for Valid and Invalid -import cats.syntax.semigroup._ // for |+| -import cats.syntax.apply._ // for mapN -import cats.syntax.validated._ // for valid and invalid +import cats.data.Validated.* // for Valid and Invalid +import cats.syntax.semigroup.* // for |+| +import cats.syntax.apply.* // for mapN +import cats.syntax.validated.* // for valid and invalid ``` Here is our complete implementation of `Predicate`, @@ -386,8 +386,8 @@ a `Predicate` from a function: ```scala mdoc:silent sealed trait Predicate[E, A] { - import Predicate._ - import Validated._ + import Predicate.* + import Validated.* def and(that: Predicate[E, A]): Predicate[E, A] = And(this, that) @@ -444,12 +444,12 @@ using inheritance: ```scala mdoc:silent import cats.Semigroup import cats.data.Validated -import cats.syntax.apply._ // for mapN -import cats.syntax.validated._ // for valid and invalid +import cats.syntax.apply.* // for mapN +import cats.syntax.validated.* // for valid and invalid ``` ```scala mdoc:silent sealed trait Check[E, A, B] { - import Check._ + import Check.* def apply(in: A)(using s: Semigroup[E]): Validated[E, B] @@ -589,8 +589,8 @@ In later sections we'll make some changes that make the library easier to use. ```scala mdoc:silent -import cats.syntax.apply._ // for mapN -import cats.syntax.validated._ // for valid and invalid +import cats.syntax.apply.* // for mapN +import cats.syntax.validated.* // for valid and invalid ``` Here's the implementation of `checkUsername`: diff --git a/src/pages/foldable-traverse/foldable-cats.md b/src/pages/foldable-traverse/foldable-cats.md index 2116e54c..fdec8fc0 100644 --- a/src/pages/foldable-traverse/foldable-cats.md +++ b/src/pages/foldable-traverse/foldable-cats.md @@ -13,7 +13,7 @@ Here is an example using `List`: ```scala mdoc:silent import cats.Foldable -import cats.instances.list._ // for Foldable +import cats.instances.list.* // for Foldable val ints = List(1, 2, 3) ``` @@ -27,7 +27,7 @@ Here is an example using `Option`, which is treated like a sequence of zero or one elements: ```scala mdoc:silent -import cats.instances.option._ // for Foldable +import cats.instances.option.* // for Foldable val maybeInt = Option(123) ``` @@ -73,7 +73,7 @@ Using `Foldable` forces us to use stack safe operations, which fixes the overflow exception: ```scala mdoc:silent -import cats.instances.lazyList._ // for Foldable +import cats.instances.lazyList.* // for Foldable ``` ```scala mdoc:silent @@ -130,7 +130,7 @@ Cats provides two methods that make use of `Monoids`: For example, we can use `combineAll` to sum over a `List[Int]`: ```scala mdoc:silent -import cats.instances.int._ // for Monoid +import cats.instances.int.* // for Monoid ``` ```scala mdoc @@ -141,7 +141,7 @@ Alternatively, we can use `foldMap` to convert each `Int` to a `String` and concatenate them: ```scala mdoc:silent -import cats.instances.string._ // for Monoid +import cats.instances.string.* // for Monoid ``` ```scala mdoc @@ -153,12 +153,12 @@ to support deep traversal of nested sequences: ```scala mdoc:invisible:reset-object import cats.Foldable -import cats.instances.list._ -import cats.instances.int._ -import cats.instances.string._ +import cats.instances.list.* +import cats.instances.int.* +import cats.instances.string.* ``` ```scala mdoc:silent -import cats.instances.vector._ // for Monoid +import cats.instances.vector.* // for Monoid val ints = List(Vector(1, 2, 3), Vector(4, 5, 6)) ``` @@ -175,7 +175,7 @@ In each case, the first argument to the method on `Foldable` becomes the receiver of the method call: ```scala mdoc:silent -import cats.syntax.foldable._ // for combineAll and foldMap +import cats.syntax.foldable.* // for combineAll and foldMap ``` ```scala mdoc diff --git a/src/pages/foldable-traverse/foldable.md b/src/pages/foldable-traverse/foldable.md index 79776895..42307f42 100644 --- a/src/pages/foldable-traverse/foldable.md +++ b/src/pages/foldable-traverse/foldable.md @@ -159,7 +159,7 @@ def sumWithMonoid[A](list: List[A]) (using monoid: Monoid[A]): A = list.foldRight(monoid.empty)(monoid.combine) -import cats.instances.int._ // for Monoid +import cats.instances.int.* // for Monoid ``` ```scala mdoc diff --git a/src/pages/foldable-traverse/traverse-cats.md b/src/pages/foldable-traverse/traverse-cats.md index 8a38ae5d..9de8a6b5 100644 --- a/src/pages/foldable-traverse/traverse-cats.md +++ b/src/pages/foldable-traverse/traverse-cats.md @@ -28,8 +28,8 @@ and use the `traverse` and `sequence` methods as described in the previous section: ```scala mdoc:invisible -import scala.concurrent._ -import scala.concurrent.duration._ +import scala.concurrent.* +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global val hostnames = List( @@ -44,8 +44,8 @@ def getUptime(hostname: String): Future[Int] = ```scala mdoc:silent import cats.Traverse -import cats.instances.future._ // for Applicative -import cats.instances.list._ // for Traverse +import cats.instances.future.* // for Applicative +import cats.instances.list.* // for Traverse val totalUptime: Future[List[Int]] = Traverse[List].traverse(hostnames)(getUptime) @@ -70,7 +70,7 @@ There are also syntax versions of the methods, imported via [`cats.syntax.traverse`][cats.syntax.traverse]: ```scala mdoc:silent -import cats.syntax.traverse._ // for sequence and traverse +import cats.syntax.traverse.* // for sequence and traverse ``` ```scala mdoc diff --git a/src/pages/foldable-traverse/traverse.md b/src/pages/foldable-traverse/traverse.md index 500abc1d..025402ba 100644 --- a/src/pages/foldable-traverse/traverse.md +++ b/src/pages/foldable-traverse/traverse.md @@ -16,8 +16,8 @@ As an example, suppose we have a list of server hostnames and a method to poll a host for its uptime: ```scala mdoc:silent -import scala.concurrent._ -import scala.concurrent.duration._ +import scala.concurrent.* +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global val hostnames = List( @@ -62,8 +62,8 @@ We can improve on things greatly using `Future.traverse`, which is tailor-made for this pattern: ```scala mdoc:invisible:reset-object -import scala.concurrent._ -import scala.concurrent.duration._ +import scala.concurrent.* +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global val hostnames = List( @@ -157,8 +157,8 @@ is equivalent to `Applicative.pure`: ```scala mdoc:silent import cats.Applicative -import cats.instances.future._ // for Applicative -import cats.syntax.applicative._ // for pure +import cats.instances.future.* // for Applicative +import cats.syntax.applicative.* // for pure List.empty[Int].pure[Future] ``` @@ -181,7 +181,7 @@ def oldCombine( is now equivalent to `Semigroupal.combine`: ```scala mdoc:silent -import cats.syntax.apply._ // for mapN +import cats.syntax.apply.* // for mapN // Combining accumulator and hostname using an Applicative: def newCombine(accum: Future[List[Int]], @@ -223,7 +223,7 @@ as shown in the following exercises. What is the result of the following? ```scala mdoc:silent -import cats.instances.vector._ // for Applicative +import cats.instances.vector.* // for Applicative listSequence(List(Vector(1, 2), Vector(3, 4))) ``` @@ -263,7 +263,7 @@ listSequence(List(Vector(1, 2), Vector(3, 4), Vector(5, 6))) Here's an example that uses `Options`: ```scala mdoc:silent -import cats.instances.option._ // for Applicative +import cats.instances.option.* // for Applicative def process(inputs: List[Int]) = listTraverse(inputs)(n => if(n % 2 == 0) Some(n) else None) @@ -297,8 +297,8 @@ Finally, here is an example that uses `Validated`: ```scala mdoc:invisible:reset import cats.Applicative -import cats.syntax.applicative._ // for pure -import cats.syntax.apply._ // for mapN +import cats.syntax.applicative.* // for pure +import cats.syntax.apply.* // for mapN def listTraverse[F[_]: Applicative, A, B] (list: List[A])(func: A => F[B]): F[List[B]] = list.foldLeft(List.empty[B].pure[F]) { (accum, item) => @@ -307,7 +307,7 @@ def listTraverse[F[_]: Applicative, A, B] ``` ```scala mdoc:silent import cats.data.Validated -import cats.instances.list._ // for Monoid +import cats.instances.list.* // for Monoid type ErrorsOr[A] = Validated[List[String], A] diff --git a/src/pages/functors/cats.md b/src/pages/functors/cats.md index 67cd5859..a0fccf60 100644 --- a/src/pages/functors/cats.md +++ b/src/pages/functors/cats.md @@ -14,8 +14,8 @@ the [`cats.instances`][cats.instances] package: ```scala mdoc:silent:reset-object import cats.Functor -import cats.instances.list._ // for Functor -import cats.instances.option._ // for Functor +import cats.instances.list.* // for Functor +import cats.instances.option.* // for Functor ``` ```scala mdoc @@ -60,8 +60,8 @@ Scala's `Function1` type doesn't have a `map` method so there are no naming conflicts: ```scala mdoc:silent -import cats.instances.function._ // for Functor -import cats.syntax.functor._ // for map +import cats.instances.function.* // for Functor +import cats.syntax.functor.* // for map ``` ```scala mdoc:silent @@ -86,8 +86,8 @@ def doMath[F[_]](start: F[Int]) (using functor: Functor[F]): F[Int] = start.map(n => n + 1 * 2) -import cats.instances.option._ // for Functor -import cats.instances.list._ // for Functor +import cats.instances.option.* // for Functor +import cats.instances.list.* // for Functor ``` ```scala mdoc diff --git a/src/pages/functors/contravariant-invariant-cats.md b/src/pages/functors/contravariant-invariant-cats.md index f6742d42..4df9b684 100644 --- a/src/pages/functors/contravariant-invariant-cats.md +++ b/src/pages/functors/contravariant-invariant-cats.md @@ -30,7 +30,7 @@ Here's an example: ```scala mdoc:silent:reset import cats.Contravariant import cats.Show -import cats.instances.string._ +import cats.instances.string.* val showString = Show[String] @@ -47,7 +47,7 @@ More conveniently, we can use which provides a `contramap` extension method: ```scala mdoc:silent -import cats.syntax.contravariant._ // for contramap +import cats.syntax.contravariant.* // for contramap ``` ```scala mdoc @@ -95,9 +95,9 @@ provided by `cats.syntax.invariant`: ```scala mdoc:silent import cats.Monoid -import cats.instances.string._ // for Monoid -import cats.syntax.invariant._ // for imap -import cats.syntax.semigroup._ // for |+| +import cats.instances.string.* // for Monoid +import cats.syntax.invariant.* // for imap +import cats.syntax.semigroup.* // for |+| given symbolMonoid: Monoid[Symbol] = Monoid[String].imap(Symbol.apply)(_.name) diff --git a/src/pages/functors/index.md b/src/pages/functors/index.md index 267b0a30..35baa49e 100644 --- a/src/pages/functors/index.md +++ b/src/pages/functors/index.md @@ -102,7 +102,7 @@ seen in `List`, `Option`, and `Either`: ```scala mdoc:silent import scala.concurrent.{Future, Await} import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ +import scala.concurrent.duration.* val future: Future[String] = Future(123). @@ -210,8 +210,8 @@ We also see this in Figure [@fig:functors:function-type-chart]: In other words, "mapping" over a `Function1` is function composition: ```scala mdoc:silent -import cats.instances.function._ // for Functor -import cats.syntax.functor._ // for map +import cats.instances.function.* // for Functor +import cats.syntax.functor.* // for map ``` ```scala mdoc:silent diff --git a/src/pages/functors/partial-unification.md b/src/pages/functors/partial-unification.md index 57b3ad93..e8532807 100644 --- a/src/pages/functors/partial-unification.md +++ b/src/pages/functors/partial-unification.md @@ -5,8 +5,8 @@ we saw a functor instance for `Function1`. ```scala mdoc:silent import cats.Functor -import cats.instances.function._ // for Functor -import cats.syntax.functor._ // for map +import cats.instances.function.* // for Functor +import cats.syntax.functor.* // for map val func1 = (x: Int) => x.toDouble val func2 = (y: Double) => y * 2 @@ -123,7 +123,7 @@ If we try this for real, however, our code won't compile: ```scala mdoc:silent -import cats.syntax.contravariant._ // for contramap +import cats.syntax.contravariant.* // for contramap ``` ```scala mdoc:fail diff --git a/src/pages/monad-transformers/index.md b/src/pages/monad-transformers/index.md index cf92a0ce..76e2adb3 100644 --- a/src/pages/monad-transformers/index.md +++ b/src/pages/monad-transformers/index.md @@ -46,7 +46,7 @@ That is, do monads *compose*? We can try to write the code but we soon hit problems: ```scala mdoc:silent -import cats.syntax.applicative._ // for pure +import cats.syntax.applicative.* // for pure ``` ```scala @@ -120,8 +120,8 @@ using the `OptionT` constructor, or more conveniently using `pure`: ```scala mdoc:silent -import cats.instances.list._ // for Monad -import cats.syntax.applicative._ // for pure +import cats.instances.list.* // for Monad +import cats.syntax.applicative.* // for pure ``` ```scala mdoc @@ -246,7 +246,7 @@ In other words, we build monad stacks from the inside out: ```scala mdoc:invisible:reset import cats.data.OptionT -import cats.syntax.applicative._ // for pure +import cats.syntax.applicative.* // for pure ``` ```scala mdoc:silent type ListOption[A] = OptionT[List, A] @@ -277,7 +277,7 @@ We can use `pure`, `map`, and `flatMap` as usual to create and transform instances: ```scala mdoc:silent -import cats.instances.either._ // for Monad +import cats.instances.either.* // for Monad ``` ```scala mdoc @@ -325,10 +325,10 @@ and our `map` and `flatMap` methods cut through three layers of abstraction: ```scala mdoc:silent -import cats.instances.future._ // for Monad +import cats.instances.future.* // for Monad import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ +import scala.concurrent.duration.* ``` ```scala mdoc:silent @@ -351,7 +351,7 @@ to make it easier to define partially applied type constructors. For example: ```scala mdoc -import cats.instances.option._ // for Monad +import cats.instances.option.* // for Monad 123.pure[EitherT[Option, String, *]] ``` @@ -452,7 +452,7 @@ and use a fusion `Future` and `Either` everywhere in our code: ```scala mdoc:invisible:reset-object import cats.data.EitherT -import cats.instances.list._ +import cats.instances.list.* import scala.concurrent.Future ``` ```scala mdoc:silent @@ -589,7 +589,7 @@ val powerLevels = Map( ) ``` ```scala mdoc:silent -import cats.instances.future._ // for Monad +import cats.instances.future.* // for Monad import scala.concurrent.ExecutionContext.Implicits.global type Response[A] = EitherT[Future, String, A] @@ -621,8 +621,8 @@ We request the power level from each ally and use `map` and `flatMap` to combine the results: ```scala mdoc:invisible:reset-object -import cats.implicits._ -import cats.data._ +import cats.implicits.* +import cats.data.* import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global @@ -664,8 +664,8 @@ We use the `value` method to unpack the monad stack and `Await` and `fold` to unpack the `Future` and `Either`: ```scala mdoc:invisible:reset -import cats.implicits._ -import cats.data._ +import cats.implicits.* +import cats.data.* import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global @@ -687,7 +687,7 @@ def getPowerLevel(ally: String): Response[Int] = { ```scala mdoc:silent import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ +import scala.concurrent.duration.* def canSpecialMove(ally1: String, ally2: String): Response[Boolean] = for { diff --git a/src/pages/monads/cats.md b/src/pages/monads/cats.md index 97f9129c..d2272f8b 100644 --- a/src/pages/monads/cats.md +++ b/src/pages/monads/cats.md @@ -18,8 +18,8 @@ Here are some examples using `pure` and `flatMap`, and `map` directly: ```scala mdoc:silent import cats.Monad -import cats.instances.option._ // for Monad -import cats.instances.list._ // for Monad +import cats.instances.option.* // for Monad +import cats.instances.list.* // for Monad ``` ```scala mdoc @@ -43,7 +43,7 @@ Cats provides instances for all the monads in the standard library (`Option`, `List`, `Vector` and so on) via [`cats.instances`][cats.instances]: ```scala mdoc:silent -import cats.instances.option._ // for Monad +import cats.instances.option.* // for Monad ``` ```scala mdoc @@ -51,7 +51,7 @@ Monad[Option].flatMap(Option(1))(a => Option(a*2)) ``` ```scala mdoc:silent -import cats.instances.list._ // for Monad +import cats.instances.list.* // for Monad ``` ```scala mdoc @@ -59,7 +59,7 @@ Monad[List].flatMap(List(1, 2, 3))(a => List(a, a*10)) ``` ```scala mdoc:silent -import cats.instances.vector._ // for Monad +import cats.instances.vector.* // for Monad ``` ```scala mdoc @@ -75,9 +75,9 @@ To work around this, Cats requires us to have an `ExecutionContext` in scope when we summon a `Monad` for `Future`: ```scala mdoc:silent -import cats.instances.future._ // for Monad -import scala.concurrent._ -import scala.concurrent.duration._ +import cats.instances.future.* // for Monad +import scala.concurrent.* +import scala.concurrent.duration.* ``` ```scala mdoc:fail @@ -129,9 +129,9 @@ We can use `pure` to construct instances of a monad. We'll often need to specify the type parameter to disambiguate the particular instance we want. ```scala mdoc:silent -import cats.instances.option._ // for Monad -import cats.instances.list._ // for Monad -import cats.syntax.applicative._ // for pure +import cats.instances.option.* // for Monad +import cats.instances.list.* // for Monad +import cats.syntax.applicative.* // for pure ``` ```scala mdoc @@ -148,14 +148,14 @@ that come wrapped in a monad of the user's choice: ```scala mdoc:silent import cats.Monad -import cats.syntax.functor._ // for map -import cats.syntax.flatMap._ // for flatMap +import cats.syntax.functor.* // for map +import cats.syntax.flatMap.* // for flatMap def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] = a.flatMap(x => b.map(y => x*x + y*y)) -import cats.instances.option._ // for Monad -import cats.instances.list._ // for Monad +import cats.instances.option.* // for Monad +import cats.instances.list.* // for Monad ``` ```scala mdoc @@ -170,7 +170,7 @@ and inserting the correct implicit conversions to use our `Monad`: ```scala mdoc:invisible:reset-object import cats.Monad -import cats.implicits._ +import cats.implicits.* ``` ```scala mdoc:silent def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] = diff --git a/src/pages/monads/custom-instances.md b/src/pages/monads/custom-instances.md index efb437fa..a9ab98a4 100644 --- a/src/pages/monads/custom-instances.md +++ b/src/pages/monads/custom-instances.md @@ -47,7 +47,7 @@ and many monads have some notion of stopping. We can write this method in terms of `flatMap`. ```scala mdoc:silent -import cats.syntax.flatMap._ // For flatMap +import cats.syntax.flatMap.* // For flatMap def retry[F[_]: Monad, A](start: A)(f: A => F[A]): F[A] = f(start).flatMap{ a => @@ -59,7 +59,7 @@ Unfortunately it is not stack-safe. It works for small input. ```scala mdoc -import cats.instances.option._ +import cats.instances.option.* retry(100)(a => if(a == 0) None else Some(a - 1)) ``` @@ -75,7 +75,7 @@ We can instead rewrite this method using `tailRecM`. ```scala mdoc:silent -import cats.syntax.functor._ // for map +import cats.syntax.functor.* // for map def retryTailRecM[F[_]: Monad, A](start: A)(f: A => F[A]): F[A] = Monad[F].tailRecM(start){ a => @@ -104,7 +104,7 @@ in terms of `iterateWhileM` and we don't have to explicitly call `tailRecM`. ```scala mdoc:silent -import cats.syntax.monad._ // for iterateWhileM +import cats.syntax.monad.* // for iterateWhileM def retryM[F[_]: Monad, A](start: A)(f: A => F[A]): F[A] = start.iterateWhileM(f)(a => true) @@ -265,8 +265,8 @@ Regardless of which version of `tailRecM` we define, we can use our `Monad` to `flatMap` and `map` on `Trees`: ```scala mdoc:silent -import cats.syntax.functor._ // for map -import cats.syntax.flatMap._ // for flatMap +import cats.syntax.functor.* // for map +import cats.syntax.flatMap.* // for flatMap ``` ```scala mdoc diff --git a/src/pages/monads/either.md b/src/pages/monads/either.md index 3eb5fdf1..0d95fce1 100644 --- a/src/pages/monads/either.md +++ b/src/pages/monads/either.md @@ -49,7 +49,7 @@ In Scala 2.12+ we can either omit this import or leave it in place without breaking anything: ```scala mdoc:silent -import cats.syntax.either._ // for map and flatMap +import cats.syntax.either.* // for map and flatMap for { a <- either1 @@ -64,7 +64,7 @@ we can also import the `asLeft` and `asRight` extension methods from [`cats.syntax.either`][cats.syntax.either]: ```scala mdoc:silent -import cats.syntax.either._ // for asRight +import cats.syntax.either.* // for asRight ``` ```scala mdoc @@ -154,7 +154,7 @@ can use `orElse` and `getOrElse` to extract values from the right side or return a default: ```scala mdoc:silent -import cats.syntax.either._ +import cats.syntax.either.* ``` ```scala mdoc @@ -241,7 +241,7 @@ object wrapper { case PasswordIncorrect(username: String) case UnexpectedError } -}; import wrapper._ +}; import wrapper.* import LoginError.* ``` diff --git a/src/pages/monads/id.md b/src/pages/monads/id.md index 617770b3..f2425e69 100644 --- a/src/pages/monads/id.md +++ b/src/pages/monads/id.md @@ -5,8 +5,8 @@ by writing a method that abstracted over different monads: ```scala mdoc:silent import cats.Monad -import cats.syntax.functor._ // for map -import cats.syntax.flatMap._ // for flatMap +import cats.syntax.functor.* // for map +import cats.syntax.flatMap.* // for flatMap def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] = for { @@ -69,8 +69,8 @@ val b = Monad[Id].flatMap(a)(_ + 1) ``` ```scala mdoc:silent -import cats.syntax.functor._ // for map -import cats.syntax.flatMap._ // for flatMap +import cats.syntax.functor.* // for map +import cats.syntax.flatMap.* // for flatMap ``` ```scala mdoc @@ -116,8 +116,8 @@ All we have to do is return the initial value: ```scala mdoc:invisible:reset-object import cats.{Id,Monad} -import cats.syntax.functor._ -import cats.syntax.flatMap._ +import cats.syntax.functor.* +import cats.syntax.flatMap.* def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] = for { x <- a diff --git a/src/pages/monads/monad-error.md b/src/pages/monads/monad-error.md index 80996fb3..73591ac2 100644 --- a/src/pages/monads/monad-error.md +++ b/src/pages/monads/monad-error.md @@ -55,7 +55,7 @@ instantiate the type class for `Either`: ```scala mdoc:silent import cats.MonadError -import cats.instances.either._ // for MonadError +import cats.instances.either.* // for MonadError type ErrorOr[A] = Either[String, A] @@ -126,14 +126,14 @@ and `ensure` via [`cats.syntax.monadError`][cats.syntax.monadError]: ```scala mdoc:invisible:reset import cats.MonadError -import cats.instances.either._ // for MonadError +import cats.instances.either.* // for MonadError type ErrorOr[A] = Either[String, A] ``` ```scala mdoc:silent -import cats.syntax.applicative._ // for pure -import cats.syntax.applicativeError._ // for raiseError etc -import cats.syntax.monadError._ // for ensure +import cats.syntax.applicative.* // for pure +import cats.syntax.applicativeError.* // for raiseError etc +import cats.syntax.monadError.* // for ensure ``` ```scala mdoc @@ -165,7 +165,7 @@ always represent errors as `Throwables`: ```scala mdoc:silent import scala.util.Try -import cats.instances.try_._ // for MonadError +import cats.instances.try_.* // for MonadError val exn: Throwable = new RuntimeException("It's all gone wrong") @@ -206,7 +206,7 @@ We can solve this using `pure` and `raiseError`. Note the use of type parameters ```scala mdoc:invisible:reset-object import cats.MonadError -import cats.implicits._ +import cats.implicits.* ``` ```scala mdoc:silent def validateAdult[F[_]](age: Int)(using me: MonadError[F, Throwable]): F[Int] = diff --git a/src/pages/monads/reader.md b/src/pages/monads/reader.md index efdee2c5..0bbf2799 100644 --- a/src/pages/monads/reader.md +++ b/src/pages/monads/reader.md @@ -184,7 +184,7 @@ def checkPassword( Reader(db => db.passwords.get(username).contains(password)) ``` ```scala mdoc:silent -import cats.syntax.applicative._ // for pure +import cats.syntax.applicative.* // for pure def checkLogin( userId: Int, diff --git a/src/pages/monads/state.md b/src/pages/monads/state.md index b7d137d3..48f0fce6 100644 --- a/src/pages/monads/state.md +++ b/src/pages/monads/state.md @@ -121,7 +121,7 @@ that only represent transformations on the state: ```scala mdoc:silent:reset-object import cats.data.State -import State._ +import State.* ``` ```scala mdoc @@ -351,7 +351,7 @@ def evalOne(sym: String): CalcState[Int] = } ``` ```scala mdoc:silent -import cats.syntax.applicative._ // for pure +import cats.syntax.applicative.* // for pure def evalAll(input: List[String]): CalcState[Int] = input.foldLeft(0.pure[CalcState]) { (a, b) => diff --git a/src/pages/monads/writer.md b/src/pages/monads/writer.md index d17d0bdf..7fa3d005 100644 --- a/src/pages/monads/writer.md +++ b/src/pages/monads/writer.md @@ -34,7 +34,7 @@ We can create a `Writer` from values of each type as follows: ```scala mdoc:silent import cats.data.Writer -import cats.instances.vector._ // for Monoid +import cats.instances.vector.* // for Monoid ``` ```scala mdoc @@ -67,8 +67,8 @@ To do this we must have a `Monoid[W]` in scope so Cats knows how to produce an empty log: ```scala mdoc:silent -import cats.instances.vector._ // for Monoid -import cats.syntax.applicative._ // for pure +import cats.instances.vector.* // for Monoid +import cats.syntax.applicative.* // for pure type Logged[A] = Writer[Vector[String], A] ``` @@ -82,7 +82,7 @@ we can create a `Writer[Unit]` using the `tell` syntax from [`cats.syntax.writer`][cats.syntax.writer]: ```scala mdoc:silent -import cats.syntax.writer._ // for tell +import cats.syntax.writer.* // for tell ``` ```scala mdoc @@ -95,7 +95,7 @@ or we can use the `writer` syntax from [`cats.syntax.writer`][cats.syntax.writer]: ```scala mdoc:silent -import cats.syntax.writer._ // for writer +import cats.syntax.writer.* // for writer ``` ```scala mdoc @@ -214,9 +214,9 @@ This makes it difficult to see which messages come from which computation: ```scala -import scala.concurrent._ -import scala.concurrent.ExecutionContext.Implicits._ -import scala.concurrent.duration._ +import scala.concurrent.* +import scala.concurrent.ExecutionContext.Implicits.* +import scala.concurrent.duration.* Await.result(Future.sequence(Vector( Future(factorial(5)), @@ -255,8 +255,8 @@ so we can use it with `pure` syntax: ```scala mdoc:silent:reset-object import cats.data.Writer -import cats.instances.vector._ -import cats.syntax.applicative._ // for pure +import cats.instances.vector.* +import cats.syntax.applicative.* // for pure type Logged[A] = Writer[Vector[String], A] ``` @@ -268,7 +268,7 @@ type Logged[A] = Writer[Vector[String], A] We'll import the `tell` syntax as well: ```scala mdoc:silent -import cats.syntax.writer._ // for tell +import cats.syntax.writer.* // for tell ``` ```scala mdoc @@ -280,7 +280,7 @@ the `Semigroup` instance for `Vector`. We need this to `map` and `flatMap` over `Logged`: ```scala mdoc:silent -import cats.instances.vector._ // for Monoid +import cats.instances.vector.* // for Monoid ``` ```scala mdoc diff --git a/src/pages/monoids/cats.md b/src/pages/monoids/cats.md index 92a3ecae..d74c9915 100644 --- a/src/pages/monoids/cats.md +++ b/src/pages/monoids/cats.md @@ -50,7 +50,7 @@ we can write the following: ```scala mdoc:silent import cats.Monoid -import cats.instances.string._ // for Monoid +import cats.instances.string.* // for Monoid ``` ```scala mdoc @@ -85,7 +85,7 @@ we import from [`cats.instances.int`][cats.instances.int]: ```scala mdoc:silent import cats.Monoid -import cats.instances.int._ // for Monoid +import cats.instances.int.* // for Monoid ``` ```scala mdoc @@ -98,8 +98,8 @@ and [`cats.instances.option`][cats.instances.option]: ```scala mdoc:silent import cats.Monoid -import cats.instances.int._ // for Monoid -import cats.instances.option._ // for Monoid +import cats.instances.int.* // for Monoid +import cats.instances.option.* // for Monoid ``` ```scala mdoc @@ -116,8 +116,8 @@ As always, unless we have a good reason to import individual instances, we can just import everything. ```scala -import cats._ -import cats.implicits._ +import cats.* +import cats.implicits.* ``` ### Monoid Syntax {#sec:monoid-syntax} @@ -128,8 +128,8 @@ Because `combine` technically comes from `Semigroup`, we access the syntax by importing from [`cats.syntax.semigroup`][cats.syntax.semigroup]: ```scala mdoc:silent -import cats.instances.string._ // for Monoid -import cats.syntax.semigroup._ // for |+| +import cats.instances.string.* // for Monoid +import cats.syntax.semigroup.* // for |+| ``` ```scala mdoc @@ -137,7 +137,7 @@ val stringResult = "Hi " |+| "there" |+| Monoid[String].empty ``` ```scala mdoc:silent -import cats.instances.int._ // for Monoid +import cats.instances.int.* // for Monoid ``` ```scala mdoc @@ -163,8 +163,8 @@ although there's not a compelling use case for this yet: ```scala mdoc:silent:reset-object import cats.Monoid -import cats.instances.int._ // for Monoid -import cats.syntax.semigroup._ // for |+| +import cats.instances.int.* // for Monoid +import cats.syntax.semigroup.* // for |+| def add(items: List[Int]): Int = items.foldLeft(Monoid[Int].empty)(_ |+| _) @@ -185,7 +185,7 @@ We can write this as a generic method that accepts an implicit `Monoid` as a par ```scala mdoc:silent:reset-object import cats.Monoid -import cats.syntax.semigroup._ // for |+| +import cats.syntax.semigroup.* // for |+| def add[A](items: List[A])(using monoid: Monoid[A]): A = items.foldLeft(monoid.empty)(_ |+| _) @@ -195,8 +195,8 @@ We can optionally use Scala's *context bound* syntax to write the same code in a ```scala mdoc:invisible:reset-object import cats.Monoid -import cats.instances.int._ // for Monoid -import cats.syntax.semigroup._ // for |+| +import cats.instances.int.* // for Monoid +import cats.syntax.semigroup.* // for |+| ``` ```scala mdoc:silent def add[A: Monoid](items: List[A]): A = @@ -206,7 +206,7 @@ def add[A: Monoid](items: List[A]): A = We can use this code to add values of type `Int` and `Option[Int]` as requested: ```scala mdoc:silent -import cats.instances.int._ // for Monoid +import cats.instances.int.* // for Monoid ``` ```scala mdoc @@ -214,7 +214,7 @@ add(List(1, 2, 3)) ``` ```scala mdoc:silent -import cats.instances.option._ // for Monoid +import cats.instances.option.* // for Monoid ``` ```scala mdoc diff --git a/src/pages/monoids/summary.md b/src/pages/monoids/summary.md index f90b1d54..233a3616 100644 --- a/src/pages/monoids/summary.md +++ b/src/pages/monoids/summary.md @@ -12,8 +12,8 @@ and the semigroup syntax to give us the `|+|` operator: ```scala mdoc:silent import cats.Monoid -import cats.instances.string._ // for Monoid -import cats.syntax.semigroup._ // for |+| +import cats.instances.string.* // for Monoid +import cats.syntax.semigroup.* // for |+| ``` ```scala mdoc @@ -24,8 +24,8 @@ With the correct instances in scope, we can set about adding anything we want: ```scala mdoc:silent -import cats.instances.int._ // for Monoid -import cats.instances.option._ // for Monoid +import cats.instances.int.* // for Monoid +import cats.instances.option.* // for Monoid ``` ```scala mdoc @@ -33,7 +33,7 @@ Option(1) |+| Option(2) ``` ```scala mdoc:silent -import cats.instances.map._ // for Monoid +import cats.instances.map.* // for Monoid val map1 = Map("a" -> 1, "b" -> 2) val map2 = Map("b" -> 3, "d" -> 4) @@ -44,7 +44,7 @@ map1 |+| map2 ``` ```scala mdoc:silent -import cats.instances.tuple._ // for Monoid +import cats.instances.tuple.* // for Monoid val tuple1 = ("hello", 123) diff --git a/src/pages/type-classes/cats.md b/src/pages/type-classes/cats.md index 8ff7c232..58d1a257 100644 --- a/src/pages/type-classes/cats.md +++ b/src/pages/type-classes/cats.md @@ -62,8 +62,8 @@ Let's import the instances of `Show` for `Int` and `String`: ```scala mdoc:reset:silent import cats.Show -import cats.instances.int._ // for Show -import cats.instances.string._ // for Show +import cats.instances.int.* // for Show +import cats.instances.string.* // for Show val showInt: Show[Int] = Show.apply[Int] val showString: Show[String] = Show.apply[String] @@ -88,7 +88,7 @@ This adds an extension method called `show` to any type for which we have an instance of `Show` in scope: ```scala mdoc:silent -import cats.syntax.show._ // for show +import cats.syntax.show.* // for show ``` ```scala mdoc @@ -107,9 +107,9 @@ exactly which instances and syntax you need in each example. However, this doesn't add value in production code. It is simpler and faster to use the following imports: -- `import cats._` imports all of Cats' type classes in one go; +- `import cats.*` imports all of Cats' type classes in one go; -- `import cats.implicits._` imports +- `import cats.implicits.*` imports all of the standard type class instances *and* all of the syntax in one go. @@ -177,9 +177,9 @@ and the interface syntax: ```scala mdoc:reset-object:silent import cats.Show -import cats.instances.int._ // for Show -import cats.instances.string._ // for Show -import cats.syntax.show._ // for show +import cats.instances.int.* // for Show +import cats.instances.string.* // for Show +import cats.syntax.show.* // for show ``` Our definition of `Cat` remains the same: diff --git a/src/pages/type-classes/equal.md b/src/pages/type-classes/equal.md index 7c8059a1..131a270b 100644 --- a/src/pages/type-classes/equal.md +++ b/src/pages/type-classes/equal.md @@ -57,7 +57,7 @@ import cats.Eq Now let's grab an instance for `Int`: ```scala mdoc:silent -import cats.instances.int._ // for Eq +import cats.instances.int.* // for Eq val eqInt = Eq[Int] ``` @@ -81,7 +81,7 @@ We can also import the interface syntax in [`cats.syntax.eq`][cats.syntax.eq] to use the `===` and `=!=` methods: ```scala mdoc:silent -import cats.syntax.eq._ // for === and =!= +import cats.syntax.eq.* // for === and =!= ``` ```scala mdoc @@ -102,8 +102,8 @@ To compare values of type `Option[Int]` we need to import instances of `Eq` for `Option` as well as `Int`: ```scala mdoc:silent -import cats.instances.int._ // for Eq -import cats.instances.option._ // for Eq +import cats.instances.int.* // for Eq +import cats.instances.option.* // for Eq ``` Now we can try some comparisons: @@ -131,7 +131,7 @@ Option(1) === Option.empty[Int] or using special syntax from [`cats.syntax.option`][cats.syntax.option]: ```scala mdoc:silent -import cats.syntax.option._ // for some and none +import cats.syntax.option.* // for some and none ``` ```scala mdoc @@ -146,7 +146,7 @@ which accepts a function of type `(A, A) => Boolean` and returns an `Eq[A]`: ```scala mdoc:silent import java.util.Date -import cats.instances.long._ // for Eq +import cats.instances.long.* // for Eq ``` ```scala mdoc:silent @@ -192,7 +192,7 @@ We'll bring instances of `Eq` into scope as we need them below: ```scala mdoc:silent:reset-object import cats.Eq -import cats.syntax.eq._ // for === +import cats.syntax.eq.* // for === ``` Our `Cat` class is the same as ever: @@ -205,8 +205,8 @@ We bring the `Eq` instances for `Int` and `String` into scope for the implementation of `Eq[Cat]`: ```scala mdoc:silent -import cats.instances.int._ // for Eq -import cats.instances.string._ // for Eq +import cats.instances.int.* // for Eq +import cats.instances.string.* // for Eq given catEqual: Eq[Cat] = Eq.instance[Cat] { (cat1, cat2) => @@ -227,7 +227,7 @@ cat1 =!= cat2 ``` ```scala mdoc:silent -import cats.instances.option._ // for Eq +import cats.instances.option.* // for Eq ``` ```scala mdoc From 9f32cc610ac2d7563ffbad020c9dee1d9324123a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 20:41:15 -0600 Subject: [PATCH 25/33] Scala 3 overhaul to 5.x --- src/pages/functors/cats.md | 34 ++---- .../functors/contravariant-invariant-cats.md | 9 +- src/pages/functors/contravariant-invariant.md | 111 +++++++----------- src/pages/functors/index.md | 11 +- src/pages/functors/partial-unification.md | 6 +- 5 files changed, 64 insertions(+), 107 deletions(-) diff --git a/src/pages/functors/cats.md b/src/pages/functors/cats.md index a0fccf60..a4604b9c 100644 --- a/src/pages/functors/cats.md +++ b/src/pages/functors/cats.md @@ -101,11 +101,10 @@ the `map` method in `cats.syntax.functor`. Here's a simplified version of the code: ```scala -extension [F[_], A](src: F[A]) { +extension [F[_], A](src: F[A]) def map[B](func: A => B) (using functor: Functor[F]): F[B] = functor.map(src)(func) -} ``` The compiler can use this extension method @@ -115,16 +114,8 @@ to insert a `map` method wherever no built-in `map` is available: foo.map(value => value + 1) ``` -Assuming `foo` has no built-in `map` method, -the compiler detects the potential error and -wraps the expression in a `FunctorOps` to fix the code: - -```scala -new FunctorOps(foo).map(value => value + 1) -``` - -The `map` method of `FunctorOps` requires -an implicit `Functor` as a parameter. +The `map` extension method requires +a using clause of `Functor` as a parameter. This means this code will only compile if we have a `Functor` for `F` in scope. If we don't, we get a compiler error: @@ -168,12 +159,9 @@ so we have to account for the dependency when we create the instance: ```scala mdoc:silent import scala.concurrent.{Future, ExecutionContext} -implicit def futureFunctor - (using ec: ExecutionContext): Functor[Future] = - new Functor[Future] { - def map[A, B](value: Future[A])(func: A => B): Future[B] = - value.map(func) - } +given futureFunctor(using ec: ExecutionContext): Functor[Future] with + def map[A, B](value: Future[A])(func: A => B): Future[B] = + value.map(func) ``` Whenever we summon a `Functor` for `Future`, @@ -188,10 +176,10 @@ This is what the expansion might look like: Functor[Future] // The compiler expands to this first: -Functor[Future](futureFunctor) +Functor[Future](using futureFunctor) // And then to this: -Functor[Future](futureFunctor(executionContext)) +Functor[Future](using futureFunctor(using executionContext)) ``` ### Exercise: Branching out with Functors @@ -200,10 +188,9 @@ Write a `Functor` for the following binary tree data type. Verify that the code works as expected on instances of `Branch` and `Leaf`: ```scala mdoc:silent -enum Tree[+A] { +enum Tree[+A]: case Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A] case Leaf[A](value: A) extends Tree[A] -} ```
@@ -238,13 +225,12 @@ The compiler can find a `Functor` instance for `Tree` but not for `Branch` or `L Let's add some smart constructors to compensate: ```scala mdoc:silent -object Tree { +object Tree: def branch[A](left: Tree[A], right: Tree[A]): Tree[A] = Branch(left, right) def leaf[A](value: A): Tree[A] = Leaf(value) -} ``` Now we can use our `Functor` properly: diff --git a/src/pages/functors/contravariant-invariant-cats.md b/src/pages/functors/contravariant-invariant-cats.md index 4df9b684..1bf2e37d 100644 --- a/src/pages/functors/contravariant-invariant-cats.md +++ b/src/pages/functors/contravariant-invariant-cats.md @@ -10,13 +10,11 @@ Here's a simplified version of the code: ``` ```scala mdoc:silent -trait Contravariant[F[_]] { +trait Contravariant[F[_]]: def contramap[A, B](fa: F[A])(f: B => A): F[B] -} -trait Invariant[F[_]] { +trait Invariant[F[_]]: def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] -} ``` ### Contravariant in Cats @@ -67,10 +65,9 @@ If you recall, this is what `Monoid` looks like: ```scala package cats -trait Monoid[A] { +trait Monoid[A]: def empty: A def combine(x: A, y: A): A -} ``` Imagine we want to produce a `Monoid` diff --git a/src/pages/functors/contravariant-invariant.md b/src/pages/functors/contravariant-invariant.md index 42cb754a..72396805 100644 --- a/src/pages/functors/contravariant-invariant.md +++ b/src/pages/functors/contravariant-invariant.md @@ -38,9 +38,8 @@ However, we can define `contramap` for the `Printable` type class we discussed in Chapter [@sec:type-classes]: ```scala mdoc:silent -trait Printable[A] { +trait Printable[A]: def format(value: A): String -} ``` A `Printable[A]` represents a transformation from `A` to `String`. @@ -48,12 +47,11 @@ Its `contramap` method accepts a function `func` of type `B => A` and creates a new `Printable[B]`: ```scala mdoc:silent:reset-object -trait Printable[A] { +trait Printable[A]: def format(value: A): String def contramap[B](func: B => A): Printable[B] = ??? -} def format[A](value: A)(using p: Printable[A]): String = p.format(value) @@ -66,15 +64,13 @@ Start with the following code template and replace the `???` with a working method body: ```scala -trait Printable[A] { +trait Printable[A]: def format(value: A): String def contramap[B](func: B => A): Printable[B] = - new Printable[B] { + new Printable[B]: def format(value: B): String = ??? - } -} ``` If you get stuck, think about the types. @@ -92,16 +88,14 @@ we use a `self` alias to distinguish the outer and inner `Printables`: ```scala mdoc:silent:reset-object -trait Printable[A] { self => - - def format(value: A): String +trait Printable[A]: + self => + def format(value: A): String - def contramap[B](func: B => A): Printable[B] = - new Printable[B] { - def format(value: B): String = - self.format(func(value)) - } -} + def contramap[B](func: B => A): Printable[B] = + new Printable[B]: + def format(value: B): String = + self.format(func(value)) def format[A](value: A)(using p: Printable[A]): String = p.format(value) @@ -167,44 +161,33 @@ we base it on the `Printable` for the type inside the `Box`. We can either write out the complete definition by hand: ```scala mdoc:invisible:reset-object -trait Printable[A] { +trait Printable[A]: self => + def format(value: A): String - def format(value: A): String + def contramap[B](func: B => A): Printable[B] = + (value: B) => self.format(func(value)) - def contramap[B](func: B => A): Printable[B] = - new Printable[B] { - def format(value: B): String = - self.format(func(value)) - } -} final case class Box[A](value: A) ``` ```scala mdoc:silent -implicit def boxPrintable[A]( - implicit p: Printable[A] -): Printable[Box[A]] = - new Printable[Box[A]] { - def format(box: Box[A]): String = - p.format(box.value) - } +given boxPrintable[A](using p: Printable[A]): Printable[Box[A]] with + def format(box: Box[A]): String = + p.format(box.value) ``` or use `contramap` to base the new instance on the implicit parameter: ```scala mdoc:invisible:reset-object -trait Printable[A] { +trait Printable[A]: self => + def format(value: A): String - def format(value: A): String - - def contramap[B](func: B => A): Printable[B] = - new Printable[B] { - def format(value: B): String = - self.format(func(value)) - } -} + def contramap[B](func: B => A): Printable[B] = + new Printable[B]: + def format(value: B): String = + self.format(func(value)) def format[A](value: A)(using p: Printable[A]): String = p.format(value) @@ -250,29 +233,25 @@ We can build our own `Codec` by enhancing `Printable` to support encoding and decoding to/from a `String`: ```scala mdoc:silent -trait Codec[A] { +trait Codec[A]: def encode(value: A): String def decode(value: String): A def imap[B](dec: A => B, enc: B => A): Codec[B] = ??? -} ``` ```scala mdoc:invisible:reset-object -trait Codec[A] { +trait Codec[A]: self => + def encode(value: A): String + def decode(value: String): A - def encode(value: A): String - def decode(value: String): A + def imap[B](dec: A => B, enc: B => A): Codec[B] = + new Codec[B]: + def encode(value: B): String = + self.encode(enc(value)) - def imap[B](dec: A => B, enc: B => A): Codec[B] = - new Codec[B] { - def encode(value: B): String = - self.encode(enc(value)) - - def decode(value: String): B = - dec(self.decode(value)) - } -} + def decode(value: String): B = + dec(self.decode(value)) ``` ```scala mdoc:silent @@ -335,20 +314,18 @@ Implement the `imap` method for `Codec` above. Here's a working implementation: ```scala mdoc:silent:reset-object -trait Codec[A] { self => - def encode(value: A): String - def decode(value: String): A +trait Codec[A]: + self => + def encode(value: A): String + def decode(value: String): A - def imap[B](dec: A => B, enc: B => A): Codec[B] = { - new Codec[B] { - def encode(value: B): String = - self.encode(enc(value)) + def imap[B](dec: A => B, enc: B => A): Codec[B] = + new Codec[B]: + def encode(value: B): String = + self.encode(enc(value)) - def decode(value: String): B = - dec(self.decode(value)) - } - } -} + def decode(value: String): B = + dec(self.decode(value)) ``` ```scala mdoc:invisible diff --git a/src/pages/functors/index.md b/src/pages/functors/index.md index 35baa49e..87704802 100644 --- a/src/pages/functors/index.md +++ b/src/pages/functors/index.md @@ -138,19 +138,19 @@ val future1 = { // the next random number in the sequence: val x = Future(r.nextInt()) - for { + for a <- x b <- x - } yield (a, b) + yield (a, b) } val future2 = { val r = new Random(0L) - for { + for a <- Future(r.nextInt()) b <- Future(r.nextInt()) - } yield (a, b) + yield (a, b) } ``` @@ -300,9 +300,8 @@ package cats ```scala mdoc:silent -trait Functor[F[_]] { +trait Functor[F[_]]: def map[A, B](fa: F[A])(f: A => B): F[B] -} ``` If you haven't seen syntax like `F[_]` before, diff --git a/src/pages/functors/partial-unification.md b/src/pages/functors/partial-unification.md index e8532807..cb4a9599 100644 --- a/src/pages/functors/partial-unification.md +++ b/src/pages/functors/partial-unification.md @@ -19,17 +19,15 @@ val func3 = func1.map(func2) (the function argument and the result type): ```scala -trait Function1[-A, +B] { +trait Function1[-A, +B]: def apply(arg: A): B -} ``` However, `Functor` accepts a type constructor with one parameter: ```scala -trait Functor[F[_]] { +trait Functor[F[_]]: def map[A, B](fa: F[A])(func: A => B): F[B] -} ``` The compiler has to fix one of the two parameters From 75330d541805289423d62d6d9cde7c2ae8856816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 20:58:55 -0600 Subject: [PATCH 26/33] Scala 3 Braceless Syntax cont. --- src/pages/applicatives/parallel.md | 6 +- src/pages/case-studies/parser/applicative.md | 6 +- src/pages/case-studies/parser/intro.md | 11 ++-- src/pages/case-studies/validation/check.md | 32 +++++----- src/pages/case-studies/validation/kleisli.md | 12 ++-- src/pages/case-studies/validation/map.md | 63 +++++++++----------- src/pages/functors/cats.md | 3 +- src/pages/monad-transformers/index.md | 3 +- src/pages/monads/custom-instances.md | 10 ++-- src/pages/monads/either.md | 3 +- src/pages/monads/eval.md | 6 +- src/pages/monads/state.md | 9 +-- src/pages/type-classes/implicits.md | 3 +- 13 files changed, 70 insertions(+), 97 deletions(-) diff --git a/src/pages/applicatives/parallel.md b/src/pages/applicatives/parallel.md index 912437e8..4d2518a4 100644 --- a/src/pages/applicatives/parallel.md +++ b/src/pages/applicatives/parallel.md @@ -115,13 +115,11 @@ by defining a `FunctionK` that converts an `Option` to a `List`. ```scala mdoc:silent import cats.arrow.FunctionK -object optionToList extends FunctionK[Option, List] { +object optionToList extends FunctionK[Option, List]: def apply[A](fa: Option[A]): List[A] = - fa match { + fa match case None => List.empty[A] case Some(a) => List(a) - } -} ``` ```scala mdoc optionToList(Some(1)) diff --git a/src/pages/case-studies/parser/applicative.md b/src/pages/case-studies/parser/applicative.md index 76eec5fe..8c2eb6a1 100644 --- a/src/pages/case-studies/parser/applicative.md +++ b/src/pages/case-studies/parser/applicative.md @@ -19,12 +19,10 @@ Define an extension method `flatten`, and an instance of `Flattener` for a neste
~~~ scala -extension [A, B, C](in: ((A, B), C)) { +extension [A, B, C](in: ((A, B), C)) def flatten: (A, B, C) = - in match { + in match case ((a, b), c) => (a, b, c) - } -} ~~~ It's fairly straightforward to define this class, but we can't abstract over it in any way. If we want to define `flatten` for a tuple like `(a, (b, c))` we need to define a new extension method. Similarly if we want to flatten a tuple with four elements. diff --git a/src/pages/case-studies/parser/intro.md b/src/pages/case-studies/parser/intro.md index d6ed4990..8052db81 100644 --- a/src/pages/case-studies/parser/intro.md +++ b/src/pages/case-studies/parser/intro.md @@ -51,12 +51,11 @@ case class Parser(parse: String => ParseResult) { @tailrec def loop(result: String, remainder: String): ParseResult = { val result1 = this.parse(remainder) - result1 match { + result1 match case _ if result1.failed => ParseResult(result, remainder) case ParseResult(result1, remainder1) => loop(result + result1, remainder1) - } } loop("", input) } @@ -115,18 +114,18 @@ Checkout the `parser-initial-a` tag to see the complete code with `~` implemente def ~(next: Parser): Parser = Parser { input => val result = this.parse(input) - result match { + result match case _ if result.failed => result case ParseResult(parsed, remainder) => val result1 = next.parse(remainder) - result1 match { + result1 match case _ if result1.failed => ParseResult("", input) case ParseResult(parsed1, remainder1) => ParseResult(parsed + parsed1, remainder1) - } - } + end match + end match } ~~~
diff --git a/src/pages/case-studies/validation/check.md b/src/pages/case-studies/validation/check.md index bce7c712..c665b8c7 100644 --- a/src/pages/case-studies/validation/check.md +++ b/src/pages/case-studies/validation/check.md @@ -240,14 +240,14 @@ import cats.syntax.semigroup.* // for |+| ``` ```scala mdoc:silent -sealed trait Check[E, A] { +sealed trait Check[E, A]: import Check.* def and(that: Check[E, A]): Check[E, A] = And(this, that) def apply(a: A)(using s: Semigroup[E]): Either[E, A] = - this match { + this match case Pure(func) => func(a) @@ -258,9 +258,9 @@ sealed trait Check[E, A] { case (Right(_), Left(e)) => e.asLeft case (Right(_), Right(_)) => a.asRight } - } -} -object Check { + end match + +object Check: final case class And[E, A]( left: Check[E, A], right: Check[E, A]) extends Check[E, A] @@ -270,7 +270,6 @@ object Check { def pure[E, A](f: A => Either[E, A]): Check[E, A] = Pure(f) -} ``` Let's see an example: @@ -336,29 +335,27 @@ import cats.syntax.apply.* // for mapN ``` ```scala mdoc:silent -sealed trait Check[E, A] { +sealed trait Check[E, A]: import Check.* def and(that: Check[E, A]): Check[E, A] = And(this, that) def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = - this match { + this match case Pure(func) => func(a) case And(left, right) => (left(a), right(a)).mapN((_, _) => a) - } -} -object Check { + +object Check: final case class And[E, A]( left: Check[E, A], right: Check[E, A]) extends Check[E, A] final case class Pure[E, A]( func: A => Validated[E, A]) extends Check[E, A] -} ```
@@ -381,7 +378,7 @@ import cats.data.Validated.* // for Valid and Invalid ``` ```scala mdoc:silent -sealed trait Check[E, A] { +sealed trait Check[E, A]: import Check.* def and(that: Check[E, A]): Check[E, A] = @@ -391,7 +388,7 @@ sealed trait Check[E, A] { Or(this, that) def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = - this match { + this match case Pure(func) => func(a) @@ -407,9 +404,9 @@ sealed trait Check[E, A] { case Invalid(e2) => Invalid(e1 |+| e2) } } - } -} -object Check { + end match + +object Check: final case class And[E, A]( left: Check[E, A], right: Check[E, A]) extends Check[E, A] @@ -420,7 +417,6 @@ object Check { final case class Pure[E, A]( func: A => Validated[E, A]) extends Check[E, A] -} ``` diff --git a/src/pages/case-studies/validation/kleisli.md b/src/pages/case-studies/validation/kleisli.md index ac96d3fd..45f674c1 100644 --- a/src/pages/case-studies/validation/kleisli.md +++ b/src/pages/case-studies/validation/kleisli.md @@ -177,7 +177,7 @@ import cats.syntax.semigroup.* // for |+| import cats.data.Validated import cats.data.Validated.{Valid, Invalid} -sealed trait Predicate[E, A] { +sealed trait Predicate[E, A]: import Predicate.* def and(that: Predicate[E, A]): Predicate[E, A] = @@ -190,7 +190,7 @@ sealed trait Predicate[E, A] { (a: A) => this(a).toEither def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = - this match { + this match case Pure(func) => func(a) @@ -206,10 +206,10 @@ sealed trait Predicate[E, A] { case Invalid(e2) => Invalid(e1 |+| e2) } } - } -} + end match +end Predicate -object Predicate { +object Predicate: final case class And[E, A]( left: Predicate[E, A], right: Predicate[E, A]) extends Predicate[E, A] @@ -226,7 +226,7 @@ object Predicate { def lift[E, A](error: E, func: A => Boolean): Predicate[E, A] = Pure(a => if(func(a)) Valid(a) else Invalid(error)) -} +end Predicate ``` Working around limitations of type inference diff --git a/src/pages/case-studies/validation/map.md b/src/pages/case-studies/validation/map.md index 15211517..46acdb9b 100644 --- a/src/pages/case-studies/validation/map.md +++ b/src/pages/case-studies/validation/map.md @@ -95,7 +95,7 @@ import cats.data.Validated.* // for Valid and Invalid ``` ```scala mdoc:silent -sealed trait Predicate[E, A] { +sealed trait Predicate[E, A]: def and(that: Predicate[E, A]): Predicate[E, A] = And(this, that) @@ -103,7 +103,7 @@ sealed trait Predicate[E, A] { Or(this, that) def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = - this match { + this match case Pure(func) => func(a) @@ -119,8 +119,7 @@ sealed trait Predicate[E, A] { case Invalid(e2) => Invalid(e1 |+| e2) } } - } -} + end match final case class And[E, A]( left: Predicate[E, A], @@ -142,13 +141,12 @@ that also allows transformation of its input. Implement `Check` with the following interface: ```scala -sealed trait Check[E, A, B] { +sealed trait Check[E, A, B]: def apply(a: A): Validated[E, B] = ??? def map[C](func: B => C): Check[E, A, C] = ??? -} ```
@@ -160,7 +158,7 @@ import cats.Semigroup import cats.data.Validated import cats.implicits.* -sealed trait Predicate[E, A] { +sealed trait Predicate[E, A]: import Predicate.* import Validated.* @@ -171,7 +169,7 @@ sealed trait Predicate[E, A] { Or(this, that) def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = - this match { + this match case Pure(func) => func(a) @@ -187,9 +185,9 @@ sealed trait Predicate[E, A] { case Invalid(e2) => Invalid(e1 |+| e2) } } - } -} -object Predicate { + end match + +object Predicate: final case class And[E, A]( left: Predicate[E, A], right: Predicate[E, A]) extends Predicate[E, A] @@ -200,7 +198,6 @@ object Predicate { final case class Pure[E, A]( func: A => Validated[E, A]) extends Predicate[E, A] -} ``` ```scala mdoc:silent import cats.Semigroup @@ -385,7 +382,7 @@ a `Predicate.apply` method to create a `Predicate` from a function: ```scala mdoc:silent -sealed trait Predicate[E, A] { +sealed trait Predicate[E, A]: import Predicate.* import Validated.* @@ -396,7 +393,7 @@ sealed trait Predicate[E, A] { Or(this, that) def apply(a: A)(using s: Semigroup[E]): Validated[E, A] = - this match { + this match case Pure(func) => func(a) @@ -412,10 +409,9 @@ sealed trait Predicate[E, A] { case Invalid(e2) => Invalid(e1 |+| e2) } } - } -} + end match -object Predicate { +object Predicate: final case class And[E, A]( left: Predicate[E, A], right: Predicate[E, A]) extends Predicate[E, A] @@ -432,7 +428,6 @@ object Predicate { def lift[E, A](err: E, fn: A => Boolean): Predicate[E, A] = Pure(a => if(fn(a)) a.valid else err.invalid) -} ``` Here is a complete implementation of `Check`. @@ -448,7 +443,7 @@ import cats.syntax.apply.* // for mapN import cats.syntax.validated.* // for valid and invalid ``` ```scala mdoc:silent -sealed trait Check[E, A, B] { +sealed trait Check[E, A, B]: import Check.* def apply(in: A)(using s: Semigroup[E]): Validated[E, B] @@ -461,51 +456,50 @@ sealed trait Check[E, A, B] { def andThen[C](next: Check[E, B, C]): Check[E, A, C] = AndThen[E, A, B, C](this, next) -} -object Check { +object Check: final case class Map[E, A, B, C]( check: Check[E, A, B], - func: B => C) extends Check[E, A, C] { + func: B => C) extends Check[E, A, C]: def apply(a: A) (using s: Semigroup[E]): Validated[E, C] = check(a) map func - } + end Map final case class FlatMap[E, A, B, C]( check: Check[E, A, B], - func: B => Check[E, A, C]) extends Check[E, A, C] { + func: B => Check[E, A, C]) extends Check[E, A, C]: def apply(a: A) (using s: Semigroup[E]): Validated[E, C] = check(a).withEither(_.flatMap(b => func(b)(a).toEither)) - } + end FlatMap final case class AndThen[E, A, B, C]( check: Check[E, A, B], - next: Check[E, B, C]) extends Check[E, A, C] { + next: Check[E, B, C]) extends Check[E, A, C]: def apply(a: A) (using s: Semigroup[E]): Validated[E, C] = check(a).withEither(_.flatMap(b => next(b).toEither)) - } + end AndThen final case class Pure[E, A, B]( - func: A => Validated[E, B]) extends Check[E, A, B] { + func: A => Validated[E, B]) extends Check[E, A, B]: def apply(a: A) (using s: Semigroup[E]): Validated[E, B] = func(a) - } + end Pure final case class PurePredicate[E, A]( - pred: Predicate[E, A]) extends Check[E, A, A] { + pred: Predicate[E, A]) extends Check[E, A, A]: def apply(a: A) (using s: Semigroup[E]): Validated[E, A] = pred(a) - } + end PurePredicate def apply[E, A](pred: Predicate[E, A]): Check[E, A, A] = PurePredicate(pred) @@ -513,7 +507,6 @@ object Check { def apply[E, A, B] (func: A => Validated[E, B]): Check[E, A, B] = Pure(func) -} ```
@@ -614,14 +607,14 @@ built up from a number of smaller components: // at least three characters long and contain a dot. val splitEmail: Check[Errors, String, (String, String)] = - Check(_.split('@') match { + Check(_.split('@') match case Array(name, domain) => (name, domain).validNel[String] case _ => "Must contain a single @ character". invalidNel[(String, String)] - }) + ) val checkLeft: Check[Errors, String, String] = Check(longerThan(0)) @@ -630,7 +623,7 @@ val checkRight: Check[Errors, String, String] = Check(longerThan(3) and contains('.')) val joinEmail: Check[Errors, (String, String), String] = - Check { case (l, r) => + Check { (l, r) => (checkLeft(l), checkRight(r)).mapN(_ + "@" + _) } diff --git a/src/pages/functors/cats.md b/src/pages/functors/cats.md index a4604b9c..06d056e6 100644 --- a/src/pages/functors/cats.md +++ b/src/pages/functors/cats.md @@ -205,12 +205,11 @@ import Tree.{Branch, Leaf} given treeFunctor: Functor[Tree] with def map[A, B](tree: Tree[A])(func: A => B): Tree[B] = - tree match { + tree match case Branch(left, right) => Branch(map(left)(func), map(right)(func)) case Leaf(value) => Leaf(func(value)) - } ``` Let's use our `Functor` to transform some `Trees`: diff --git a/src/pages/monad-transformers/index.md b/src/pages/monad-transformers/index.md index 76e2adb3..ab47b64c 100644 --- a/src/pages/monad-transformers/index.md +++ b/src/pages/monad-transformers/index.md @@ -482,10 +482,9 @@ type Logged[A] = Writer[List[String], A] // Methods generally return untransformed stacks: def parseNumber(str: String): Logged[Option[Int]] = - util.Try(str.toInt).toOption match { + util.Try(str.toInt).toOption match case Some(num) => Writer(List(s"Read $str"), Some(num)) case None => Writer(List(s"Failed on $str"), None) - } // Consumers use monad transformers locally to simplify composition: def addAll(a: String, b: String, c: String): Logged[Option[Int]] = { diff --git a/src/pages/monads/custom-instances.md b/src/pages/monads/custom-instances.md index a9ab98a4..21cf1411 100644 --- a/src/pages/monads/custom-instances.md +++ b/src/pages/monads/custom-instances.md @@ -173,12 +173,11 @@ given treeMonad: Monad[Tree] with def flatMap[A, B](tree: Tree[A]) (func: A => Tree[B]): Tree[B] = - tree match { + tree match case Branch(l, r) => Branch(flatMap(l)(func), flatMap(r)(func)) case Leaf(value) => func(value) - } def tailRecM[A, B](a: A) (func: A => Tree[Either[A, B]]): Tree[B] = @@ -224,12 +223,11 @@ given treeMonad: Monad[Tree] with def flatMap[A, B](tree: Tree[A]) (func: A => Tree[B]): Tree[B] = - tree match { + tree match case Branch(l, r) => Branch(flatMap(l)(func), flatMap(r)(func)) case Leaf(value) => func(value) - } def tailRecM[A, B](arg: A)( func: A => Tree[Either[A, B]] @@ -238,7 +236,7 @@ given treeMonad: Monad[Tree] with def loop( open: List[Tree[Either[A, B]]], closed: List[Option[Tree[B]]]): List[Tree[B]] = - open match { + open match case Branch(l, r) :: next => loop(l :: r :: next, None :: closed) @@ -255,7 +253,7 @@ given treeMonad: Monad[Tree] with branch(left, right) :: tail } } - } + end match loop(List(func(arg)), Nil).head } diff --git a/src/pages/monads/either.md b/src/pages/monads/either.md index 0d95fce1..72a27eba 100644 --- a/src/pages/monads/either.md +++ b/src/pages/monads/either.md @@ -261,7 +261,7 @@ on any pattern matching we do: ```scala mdoc:silent // Choose error-handling behaviour based on type: def handleError(error: LoginError): Unit = - error match { + error match case UserNotFound(u) => println(s"User not found: $u") @@ -270,7 +270,6 @@ def handleError(error: LoginError): Unit = case UnexpectedError => println(s"Unexpected error") - } ``` ```scala mdoc diff --git a/src/pages/monads/eval.md b/src/pages/monads/eval.md index d4a0dd7d..0a33e23b 100644 --- a/src/pages/monads/eval.md +++ b/src/pages/monads/eval.md @@ -310,12 +310,11 @@ Make it so using `Eval`: ```scala mdoc:silent def foldRight[A, B](as: List[A], acc: B)(fn: (A, B) => B): B = - as match { + as match case head :: tail => fn(head, foldRight(tail, acc)(fn)) case Nil => acc - } ```
@@ -330,12 +329,11 @@ import cats.Eval def foldRightEval[A, B](as: List[A], acc: Eval[B]) (fn: (A, Eval[B]) => Eval[B]): Eval[B] = - as match { + as match case head :: tail => Eval.defer(fn(head, foldRightEval(tail, acc)(fn))) case Nil => acc - } ``` We can redefine `foldRight` simply in terms of `foldRightEval` diff --git a/src/pages/monads/state.md b/src/pages/monads/state.md index 48f0fce6..32616453 100644 --- a/src/pages/monads/state.md +++ b/src/pages/monads/state.md @@ -234,13 +234,12 @@ type CalcState[A] = State[List[Int], A] ``` ```scala def evalOne(sym: String): CalcState[Int] = - sym match { + sym match case "+" => operator(_ + _) case "-" => operator(_ - _) case "*" => operator(_ * _) case "/" => operator(_ / _) case num => operand(num.toInt) - } ``` Let's look at `operand` first. @@ -275,13 +274,12 @@ def operator(func: (Int, Int) => Int): CalcState[Int] = ```scala mdoc:invisible def evalOne(sym: String): CalcState[Int] = - sym match { + sym match case "+" => operator(_ + _) case "-" => operator(_ - _) case "*" => operator(_ * _) case "/" => operator(_ / _) case num => operand(num.toInt) - } ```
@@ -342,13 +340,12 @@ def operator(func: (Int, Int) => Int): CalcState[Int] = sys.error("Fail!") } def evalOne(sym: String): CalcState[Int] = - sym match { + sym match case "+" => operator(_ + _) case "-" => operator(_ - _) case "*" => operator(_ * _) case "/" => operator(_ / _) case num => operand(num.toInt) - } ``` ```scala mdoc:silent import cats.syntax.applicative.* // for pure diff --git a/src/pages/type-classes/implicits.md b/src/pages/type-classes/implicits.md index e2e53d63..b0eace35 100644 --- a/src/pages/type-classes/implicits.md +++ b/src/pages/type-classes/implicits.md @@ -169,10 +169,9 @@ Here is the same code written out as a `given` with a `using` clause: ```scala mdoc:silent given optionWriter[A](using writer: JsonWriter[A]): JsonWriter[Option[A]] with def write(option: Option[A]): Json = - option match { + option match case Some(aValue) => writer.write(aValue) case None => Json.JsNull - } ``` This method *constructs* a `JsonWriter` for `Option[A]` by From e8fdd1bcb160048e21623719e85237c625f0a4ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 21:25:57 -0600 Subject: [PATCH 27/33] Scala 3 Braceless Syntax cont. --- src/pages/applicatives/applicative.md | 6 +- src/pages/applicatives/parallel.md | 3 +- src/pages/applicatives/semigroupal.md | 3 +- src/pages/case-studies/crdt/abstraction.md | 40 ++++------- src/pages/case-studies/crdt/g-counter.md | 9 +-- src/pages/case-studies/crdt/generalisation.md | 36 +++++----- src/pages/case-studies/parser/applicative.md | 14 ++-- .../case-studies/parser/error-handling.md | 10 +-- src/pages/case-studies/parser/intro.md | 15 +--- src/pages/case-studies/testing/index.md | 70 ++++++------------- src/pages/case-studies/validation/check.md | 12 ++-- src/pages/case-studies/validation/kleisli.md | 3 +- src/pages/case-studies/validation/map.md | 30 +++----- src/pages/foldable-traverse/traverse-cats.md | 3 +- src/pages/foldable-traverse/traverse.md | 3 +- src/pages/monad-transformers/index.md | 3 +- src/pages/monads/either.md | 8 +-- src/pages/monads/index.md | 12 ++-- src/pages/monads/monad-error.md | 3 +- 19 files changed, 100 insertions(+), 183 deletions(-) diff --git a/src/pages/applicatives/applicative.md b/src/pages/applicatives/applicative.md index 3976df38..db0edaf3 100644 --- a/src/pages/applicatives/applicative.md +++ b/src/pages/applicatives/applicative.md @@ -24,16 +24,14 @@ introduced in Chapter [@sec:monads]. Here's a simplified definition in code: ```scala -trait Apply[F[_]] extends Semigroupal[F] with Functor[F] { +trait Apply[F[_]] extends Semigroupal[F] with Functor[F]: def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ap(map(fa)(a => (b: B) => (a, b)))(fb) -} -trait Applicative[F[_]] extends Apply[F] { +trait Applicative[F[_]] extends Apply[F]: def pure[A](a: A): F[A] -} ``` Breaking this down, the `ap` method applies a parameter `fa` diff --git a/src/pages/applicatives/parallel.md b/src/pages/applicatives/parallel.md index 4d2518a4..3f9191a0 100644 --- a/src/pages/applicatives/parallel.md +++ b/src/pages/applicatives/parallel.md @@ -88,13 +88,12 @@ Let's dig into how `Parallel` works. The definition below is the core of `Parallel`. ```scala -trait Parallel[M[_]] { +trait Parallel[M[_]]: type F[_] def applicative: Applicative[F] def monad: Monad[M] def parallel: ~>[M, F] -} ``` This tells us if there is a `Parallel` instance for some type constructor `M` then: diff --git a/src/pages/applicatives/semigroupal.md b/src/pages/applicatives/semigroupal.md index b6762ea3..c1aca06a 100644 --- a/src/pages/applicatives/semigroupal.md +++ b/src/pages/applicatives/semigroupal.md @@ -7,9 +7,8 @@ a `Semigroupal[F]` allows us to combine them to form an `F[(A, B)]`. Its definition in Cats is: ```scala -trait Semigroupal[F[_]] { +trait Semigroupal[F[_]]: def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] -} ``` As we discussed at the beginning of this chapter, diff --git a/src/pages/case-studies/crdt/abstraction.md b/src/pages/case-studies/crdt/abstraction.md index b7dc8dda..64731507 100644 --- a/src/pages/case-studies/crdt/abstraction.md +++ b/src/pages/case-studies/crdt/abstraction.md @@ -27,29 +27,25 @@ represent the key and value types of the map abstraction. ```scala mdoc:reset-object:invisible import cats.kernel.CommutativeMonoid -trait BoundedSemiLattice[A] extends CommutativeMonoid[A] { +trait BoundedSemiLattice[A] extends CommutativeMonoid[A]: def combine(a1: A, a2: A): A def empty: A -} -object BoundedSemiLattice { - given intInstance: BoundedSemiLattice[Int] with - def combine(a1: Int, a2: Int): Int = - a1 max a2 +given intInstance: BoundedSemiLattice[Int] with + def combine(a1: Int, a2: Int): Int = + a1 max a2 - val empty: Int = - 0 + val empty: Int = 0 - given setInstance[A]: BoundedSemiLattice[Set[A]] with - def combine(a1: Set[A], a2: Set[A]): Set[A] = - a1 union a2 +given setInstance[A]: BoundedSemiLattice[Set[A]] with + def combine(a1: Set[A], a2: Set[A]): Set[A] = + a1 union a2 - val empty: Set[A] = - Set.empty[A] -} + val empty: Set[A] = + Set.empty[A] ``` ```scala mdoc:silent -trait GCounter[F[_,_],K, V] { +trait GCounter[F[_,_],K, V]: def increment(f: F[K, V])(k: K, v: V) (using m: CommutativeMonoid[V]): F[K, V] @@ -58,13 +54,11 @@ trait GCounter[F[_,_],K, V] { def total(f: F[K, V]) (using m: CommutativeMonoid[V]): V -} -object GCounter { +object GCounter: def apply[F[_,_], K, V] (using counter: GCounter[F, K, V]) = counter -} ``` Try defining an instance of this type class for `Map`. @@ -114,7 +108,7 @@ val counter = GCounter[Map, String, Int] ```scala mdoc val merged = counter.merge(g1, g2) -val total = counter.total(merged) +val total = counter.total(merged)(using intInstance) ``` The implementation strategy @@ -133,7 +127,7 @@ for any type that has a `KeyValueStore` instance. Here's the code for such a type class: ```scala mdoc:silent -trait KeyValueStore[F[_,_]] { +trait KeyValueStore[F[_,_]]: def put[K, V](f: F[K, V])(k: K, v: V): F[K, V] def get[K, V](f: F[K, V])(k: K): Option[V] @@ -142,7 +136,6 @@ trait KeyValueStore[F[_,_]] { get(f)(k).getOrElse(default) def values[K, V](f: F[K, V]): List[V] -} ``` Implement your own instance for `Map`. @@ -197,9 +190,7 @@ instances of `KeyValueStore` and `CommutativeMonoid` using an `implicit def`: ```scala mdoc:silent -implicit def gcounterInstance[F[_,_], K, V] - (using kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]): GCounter[F, K, V] = - new GCounter[F, K, V] { +given gcounterInstance[F[_,_], K, V](using kvs: KeyValueStore[F], km: CommutativeMonoid[F[K, V]]): GCounter[F, K, V] with def increment(f: F[K, V])(key: K, value: V) (using m: CommutativeMonoid[V]): F[K, V] = { val total = f.getOrElse(key, m.empty) |+| value @@ -212,7 +203,6 @@ implicit def gcounterInstance[F[_,_], K, V] def total(f: F[K, V])(using m: CommutativeMonoid[V]): V = f.values.combineAll - } ``` The complete code for this case study is quite long, diff --git a/src/pages/case-studies/crdt/g-counter.md b/src/pages/case-studies/crdt/g-counter.md index 6d413517..720c41b0 100644 --- a/src/pages/case-studies/crdt/g-counter.md +++ b/src/pages/case-studies/crdt/g-counter.md @@ -100,7 +100,7 @@ We can implement a GCounter with the following interface, where we represent machine IDs as `Strings`. ```scala mdoc:reset-object:silent -final case class GCounter(counters: Map[String, Int]) { +final case class GCounter(counters: Map[String, Int]): def increment(machine: String, amount: Int) = ??? @@ -109,7 +109,6 @@ final case class GCounter(counters: Map[String, Int]) { def total: Int = ??? -} ``` Finish the implementation! @@ -119,7 +118,7 @@ Hopefully the description above was clear enough that you can get to an implementation like the one below. ```scala mdoc:silent:reset-object -final case class GCounter(counters: Map[String, Int]) { +final case class GCounter(counters: Map[String, Int]): def increment(machine: String, amount: Int) = { val value = amount + counters.getOrElse(machine, 0) GCounter(counters + (machine -> value)) @@ -131,8 +130,6 @@ final case class GCounter(counters: Map[String, Int]) { k -> (v max that.counters.getOrElse(k, 0)) }) - def total: Int = - counters.values.sum -} + def total: Int = counters.values.sum ``` diff --git a/src/pages/case-studies/crdt/generalisation.md b/src/pages/case-studies/crdt/generalisation.md index 727550fa..cf15bb93 100644 --- a/src/pages/case-studies/crdt/generalisation.md +++ b/src/pages/case-studies/crdt/generalisation.md @@ -113,10 +113,9 @@ our own `BoundedSemiLattice` type class. ```scala mdoc:silent import cats.kernel.CommutativeMonoid -trait BoundedSemiLattice[A] extends CommutativeMonoid[A] { +trait BoundedSemiLattice[A] extends CommutativeMonoid[A]: def combine(a1: A, a2: A): A def empty: A -} ``` In the implementation above, @@ -146,28 +145,26 @@ import cats.kernel.CommutativeMonoid ``` ```scala mdoc:silent -object wrapper { - trait BoundedSemiLattice[A] extends CommutativeMonoid[A] { +object wrapper: + trait BoundedSemiLattice[A] extends CommutativeMonoid[A]: def combine(a1: A, a2: A): A def empty: A - } - object BoundedSemiLattice { - given intInstance: BoundedSemiLattice[Int] with - def combine(a1: Int, a2: Int): Int = - a1 max a2 + given intInstance: BoundedSemiLattice[Int] with + def combine(a1: Int, a2: Int): Int = + a1 max a2 - val empty: Int = - 0 + val empty: Int = + 0 - given setInstance[A]: BoundedSemiLattice[Set[A]] with - def combine(a1: Set[A], a2: Set[A]): Set[A] = - a1 union a2 + given setInstance[A]: BoundedSemiLattice[Set[A]] with + def combine(a1: Set[A], a2: Set[A]): Set[A] = + a1 union a2 - val empty: Set[A] = - Set.empty[A] - } -}; import wrapper.* + val empty: Set[A] = + Set.empty[A] + +import wrapper.* ``` @@ -197,7 +194,7 @@ import cats.instances.map.* // for Monoid import cats.syntax.semigroup.* // for |+| import cats.syntax.foldable.* // for combineAll -final case class GCounter[A](counters: Map[String,A]) { +final case class GCounter[A](counters: Map[String,A]): def increment(machine: String, amount: A) (using m: CommutativeMonoid[A]): GCounter[A] = { val value = amount |+| counters.getOrElse(machine, m.empty) @@ -210,7 +207,6 @@ final case class GCounter[A](counters: Map[String,A]) { def total(using m: CommutativeMonoid[A]): A = this.counters.values.toList.combineAll -} ``` diff --git a/src/pages/case-studies/parser/applicative.md b/src/pages/case-studies/parser/applicative.md index 8c2eb6a1..4cd99f06 100644 --- a/src/pages/case-studies/parser/applicative.md +++ b/src/pages/case-studies/parser/applicative.md @@ -143,13 +143,12 @@ Let's implement an instance of Scalaz's `Applicative` type class for our `Parser Define a typeclass instance of `Applicative` for `Parser`. You must implement the following trait: ~~~ scala -Applicative[Parser] { +Applicative[Parser] with def point[A](a: => A): Parser[A] = ??? def ap[A, B](fa: => Parser[A])(f: => Parser[A => B]): Parser[B] = ??? -} ~~~ Hints: @@ -166,25 +165,24 @@ The usual place to define typeclass instances is as implicit elements on the com val identity: Parser[Unit] = Parser { input => Success(Unit, input) } -implicit object applicativeInstance extends Applicative[Parser] { +given applicativeInstance: Applicative[Parser] with def point[A](a: => A): Parser[A] = identity map (_ => a) def ap[A, B](fa: => Parser[A])(f: => Parser[A => B]): Parser[B] = Parser { input => - f.parse(input) match { + f.parse(input) match case fail @ Failure(_) => fail case Success(aToB, remainder) => - fa.parse(remainder) match { + fa.parse(remainder) match case fail @ Failure(_) => fail case Success(a, remainder1) => Success(aToB(a), remainder1) - } - } + end match + end match } -} ~~~ Checkout the `parser-applicative` tag to see the full code and tests. diff --git a/src/pages/case-studies/parser/error-handling.md b/src/pages/case-studies/parser/error-handling.md index 8296fe6c..3abf3bec 100644 --- a/src/pages/case-studies/parser/error-handling.md +++ b/src/pages/case-studies/parser/error-handling.md @@ -129,11 +129,8 @@ Implement a parser for digits using the regular expression `"[0-9]"` to match a ~~~ scala package underscore.parser -object NumericParser { - +object NumericParser: val digits = Parser.regex("[0-9]").+ - -} ~~~ @@ -184,14 +181,11 @@ If `add` was a normal method we'ld only print `"Hi"` once. If we make the parameter of `~` and `|` call-by-name, our `Parser` will work. Try it and you'll see another issue---the way the grammar is written we'll stop after parsing the first number. (Try `expression.parse("123+456")` and you'll see.) The solution is to rewrite the grammar so we look for compound expressions first and we proceed left-to-right. ~~~ scala -object NumericParser { - +object NumericParser: val digits = Parser.regex("[0-9]").+ def expression: Parser = (digits ~ Parser.string("+") ~ expression) | (digits ~ Parser.string("-") ~ expression) | digits - -} ~~~ The code is tagged with `parser-numeric-expression`. diff --git a/src/pages/case-studies/parser/intro.md b/src/pages/case-studies/parser/intro.md index 8052db81..dafbdf52 100644 --- a/src/pages/case-studies/parser/intro.md +++ b/src/pages/case-studies/parser/intro.md @@ -32,18 +32,14 @@ package underscore.parser import scala.annotation.tailrec -case class ParseResult(result: String, remainder: String) { - +case class ParseResult(result: String, remainder: String): def failed: Boolean = result.isEmpty def success: Boolean = !failed -} - -case class Parser(parse: String => ParseResult) { - +case class Parser(parse: String => ParseResult): def ~(next: Parser): Parser = ??? def `*`: Parser = @@ -60,10 +56,7 @@ case class Parser(parse: String => ParseResult) { loop("", input) } -} - -object Parser { - +object Parser: def string(literal: String): Parser = Parser { input => if(input.startsWith(literal)) @@ -71,8 +64,6 @@ object Parser { else ParseResult("", input) } - -} ~~~ What does the code do? The first thing is to look at what a `Parser` is. It is basically a wrapper around a function `String => ParseResult`. The `String` parameter is the input to parse, and returned is the result of parsing that `String`. diff --git a/src/pages/case-studies/testing/index.md b/src/pages/case-studies/testing/index.md index 0017831d..2843fa58 100644 --- a/src/pages/case-studies/testing/index.md +++ b/src/pages/case-studies/testing/index.md @@ -15,9 +15,8 @@ that polls remote servers for their uptime: ```scala mdoc:silent import scala.concurrent.Future -trait UptimeClient { +trait UptimeClient: def getUptime(hostname: String): Future[Int] -} ``` We'll also have an `UptimeService` that maintains a list of servers @@ -29,10 +28,9 @@ import cats.instances.list.* // for Traverse import cats.syntax.traverse.* // for traverse import scala.concurrent.ExecutionContext.Implicits.global -class UptimeService(client: UptimeClient) { +class UptimeService(client: UptimeClient): def getTotalUptime(hostnames: List[String]): Future[Int] = hostnames.traverse(client.getUptime).map(_.sum) -} ``` We've modelled `UptimeClient` as a trait @@ -42,10 +40,9 @@ that allows us to provide dummy data rather than calling out to actual servers: ```scala mdoc:silent -class TestUptimeClient(hosts: Map[String, Int]) extends UptimeClient { +class TestUptimeClient(hosts: Map[String, Int]) extends UptimeClient: def getUptime(hostname: String): Future[Int] = Future.successful(hosts.getOrElse(hostname, 0)) -} ``` Now, suppose we're writing unit tests for `UptimeService`. @@ -89,13 +86,11 @@ an asynchronous one for use in production and a synchronous one for use in our unit tests: ```scala -trait RealUptimeClient extends UptimeClient { +trait RealUptimeClient extends UptimeClient: def getUptime(hostname: String): Future[Int] -} -trait TestUptimeClient extends UptimeClient { +trait TestUptimeClient extends UptimeClient: def getUptime(hostname: String): Int -} ``` The question is: what result type should we give @@ -103,9 +98,8 @@ to the abstract method in `UptimeClient`? We need to abstract over `Future[Int]` and `Int`: ```scala -trait UptimeClient { +trait UptimeClient: def getUptime(hostname: String): ??? -} ``` At first this may seem difficult. @@ -145,17 +139,14 @@ import scala.concurrent.Future ```scala mdoc:silent import cats.Id -trait UptimeClient[F[_]] { +trait UptimeClient[F[_]]: def getUptime(hostname: String): F[Int] -} -trait RealUptimeClient extends UptimeClient[Future] { +trait RealUptimeClient extends UptimeClient[Future]: def getUptime(hostname: String): Future[Int] -} -trait TestUptimeClient extends UptimeClient[Id] { +trait TestUptimeClient extends UptimeClient[Id]: def getUptime(hostname: String): Id[Int] -} ``` Note that, because `Id[A]` is just a simple alias for `A`, @@ -166,18 +157,15 @@ as `Id[Int]`---we can simply write `Int` instead: import scala.concurrent.Future import cats.Id -trait UptimeClient[F[_]] { +trait UptimeClient[F[_]]: def getUptime(hostname: String): F[Int] -} -trait RealUptimeClient extends UptimeClient[Future] { +trait RealUptimeClient extends UptimeClient[Future]: def getUptime(hostname: String): Future[Int] -} ``` ```scala mdoc:silent -trait TestUptimeClient extends UptimeClient[Id] { +trait TestUptimeClient extends UptimeClient[Id]: def getUptime(hostname: String): Int -} ``` Of course, technically speaking @@ -200,20 +188,17 @@ the call to `Future.successful`: import scala.concurrent.Future import cats.Id -trait UptimeClient[F[_]] { +trait UptimeClient[F[_]]: def getUptime(hostname: String): F[Int] -} -trait RealUptimeClient extends UptimeClient[Future] { +trait RealUptimeClient extends UptimeClient[Future]: def getUptime(hostname: String): Future[Int] -} ``` ```scala mdoc:silent class TestUptimeClient(hosts: Map[String, Int]) - extends UptimeClient[Id] { + extends UptimeClient[Id]: def getUptime(hostname: String): Int = hosts.getOrElse(hostname, 0) -} ``` @@ -237,11 +222,10 @@ Starting with the method signatures: The code should look like this: ```scala -class UptimeService[F[_]](client: UptimeClient[F]) { +class UptimeService[F[_]](client: UptimeClient[F]): def getTotalUptime(hostnames: List[String]): F[Int] = ??? // hostnames.traverse(client.getUptime).map(_.sum) -} ``` @@ -271,20 +255,18 @@ We can write this as an implicit parameter: import cats.syntax.traverse.* // for traverse import cats.instances.list.* -trait UptimeClient[F[_]] { +trait UptimeClient[F[_]]: def getUptime(hostname: String): F[Int] -} ``` ```scala mdoc:silent import cats.Applicative import cats.syntax.functor.* // for map class UptimeService[F[_]](client: UptimeClient[F]) - (using a: Applicative[F]) { + (using a: Applicative[F]): def getTotalUptime(hostnames: List[String]): F[Int] = hostnames.traverse(client.getUptime).map(_.sum) -} ``` or more tersely as a context bound: @@ -295,17 +277,15 @@ import cats.syntax.functor.* import cats.syntax.traverse.* import cats.instances.list.* -trait UptimeClient[F[_]] { +trait UptimeClient[F[_]]: def getUptime(hostname: String): F[Int] -} ``` ```scala mdoc:silent class UptimeService[F[_]: Applicative] - (client: UptimeClient[F]) { + (client: UptimeClient[F]): def getTotalUptime(hostnames: List[String]): F[Int] = hostnames.traverse(client.getUptime).map(_.sum) -} ``` Note that we need to import `cats.syntax.functor` @@ -331,24 +311,20 @@ import cats.syntax.functor.* // for map import cats.syntax.traverse.* // for traverse import scala.concurrent.Future -trait UptimeClient[F[_]] { +trait UptimeClient[F[_]]: def getUptime(hostname: String): F[Int] -} trait RealUptimeClient extends UptimeClient[Future] class TestUptimeClient(hosts: Map[String, Int]) - extends UptimeClient[Id] { + extends UptimeClient[Id]: def getUptime(hostname: String): Int = hosts.getOrElse(hostname, 0) - } class UptimeService[F[_]: Applicative] - (client: UptimeClient[F]) { - + (client: UptimeClient[F]): def getTotalUptime(hostnames: List[String]): F[Int] = hostnames.traverse(client.getUptime).map(_.sum) -} ``` ```scala mdoc:silent def testTotalUptime() = { diff --git a/src/pages/case-studies/validation/check.md b/src/pages/case-studies/validation/check.md index c665b8c7..ed3ddf99 100644 --- a/src/pages/case-studies/validation/check.md +++ b/src/pages/case-studies/validation/check.md @@ -28,11 +28,10 @@ We will probably want to add custom methods to `Check` so let's declare it as a `trait` instead of a type alias: ```scala mdoc:silent:reset-object -trait Check[E, A] { +trait Check[E, A]: def apply(value: A): Either[E, A] // other methods... -} ``` As we said in [Essential Scala][link-essential-scala], @@ -56,12 +55,11 @@ Think about implementing this method now. You should hit some problems. Read on when you do! ```scala mdoc:silent:reset-object -trait Check[E, A] { +trait Check[E, A]: def and(that: Check[E, A]): Check[E, A] = ??? // other methods... -} ``` The problem is: what do you do when *both* checks fail? @@ -136,7 +134,7 @@ import cats.syntax.semigroup.* // for |+| ``` ```scala mdoc:silent -final case class CheckF[E, A](func: A => Either[E, A]) { +final case class CheckF[E, A](func: A => Either[E, A]): def apply(a: A): Either[E, A] = func(a) @@ -150,7 +148,6 @@ final case class CheckF[E, A](func: A => Either[E, A]) { case (Right(_), Right(_)) => a.asRight } } -} ``` Let's test the behaviour we get. @@ -195,7 +192,7 @@ What happens if we create instances of `CheckF[Nothing, A]`? import cats.Semigroup import cats.syntax.semigroup.* import cats.syntax.either.* -final case class CheckF[E, A](func: A => Either[E, A]) { +final case class CheckF[E, A](func: A => Either[E, A]): def apply(a: A): Either[E, A] = func(a) @@ -209,7 +206,6 @@ final case class CheckF[E, A](func: A => Either[E, A]) { case (Right(_), Right(_)) => a.asRight } } -} ``` ```scala mdoc:silent val a: CheckF[Nothing, Int] = diff --git a/src/pages/case-studies/validation/kleisli.md b/src/pages/case-studies/validation/kleisli.md index 45f674c1..a26e3d8b 100644 --- a/src/pages/case-studies/validation/kleisli.md +++ b/src/pages/case-studies/validation/kleisli.md @@ -125,7 +125,7 @@ Like `apply`, the method must accept an implicit `Semigroup`: import cats.Semigroup import cats.data.Validated -sealed trait Predicate[E, A] { +sealed trait Predicate[E, A]: def run(using s: Semigroup[E]): A => Either[E, A] = (a: A) => this(a).toEither @@ -133,7 +133,6 @@ sealed trait Predicate[E, A] { ??? // etc... // other methods... -} ``` diff --git a/src/pages/case-studies/validation/map.md b/src/pages/case-studies/validation/map.md index 46acdb9b..c911044b 100644 --- a/src/pages/case-studies/validation/map.md +++ b/src/pages/case-studies/validation/map.md @@ -205,35 +205,32 @@ import cats.data.Validated ``` ```scala mdoc:silent -sealed trait Check[E, A, B] { +sealed trait Check[E, A, B]: import Check.* def apply(in: A)(using s: Semigroup[E]): Validated[E, B] def map[C](f: B => C): Check[E, A, C] = Map[E, A, B, C](this, f) -} -object Check { +object Check: final case class Map[E, A, B, C]( check: Check[E, A, B], - func: B => C) extends Check[E, A, C] { + func: B => C) extends Check[E, A, C]: def apply(in: A)(using s: Semigroup[E]): Validated[E, C] = check(in).map(func) - } + end Map final case class Pure[E, A]( - pred: Predicate[E, A]) extends Check[E, A, A] { + pred: Predicate[E, A]) extends Check[E, A, A]: def apply(in: A)(using s: Semigroup[E]): Validated[E, A] = pred(in) - } + end Pure def apply[E, A](pred: Predicate[E, A]): Check[E, A, A] = Pure(pred) -} - ``` @@ -285,22 +282,20 @@ import cats.data.Validated ``` ```scala mdoc:silent -sealed trait Check[E, A, B] { +sealed trait Check[E, A, B]: def apply(in: A)(using s: Semigroup[E]): Validated[E, B] def flatMap[C](f: B => Check[E, A, C]) = FlatMap[E, A, B, C](this, f) // other methods... -} final case class FlatMap[E, A, B, C]( check: Check[E, A, B], - func: B => Check[E, A, C]) extends Check[E, A, C] { + func: B => Check[E, A, C]) extends Check[E, A, C]: def apply(a: A)(using s: Semigroup[E]): Validated[E, C] = check(a).withEither(_.flatMap(b => func(b)(a).toEither)) -} // other data types... ``` @@ -322,9 +317,8 @@ A `Check` is basically a function `A => Validated[E, B]` so we can define an analagous `andThen` method: ```scala -trait Check[E, A, B] { +trait Check[E, A, B]: def andThen[C](that: Check[E, B, C]): Check[E, A, C] -} ``` Implement `andThen` now! @@ -338,20 +332,18 @@ import cats.Semigroup import cats.data.Validated ``` ```scala mdoc:silent -sealed trait Check[E, A, B] { +sealed trait Check[E, A, B]: def apply(in: A)(using s: Semigroup[E]): Validated[E, B] def andThen[C](that: Check[E, B, C]): Check[E, A, C] = AndThen[E, A, B, C](this, that) -} final case class AndThen[E, A, B, C]( check1: Check[E, A, B], - check2: Check[E, B, C]) extends Check[E, A, C] { + check2: Check[E, B, C]) extends Check[E, A, C]: def apply(a: A)(using s: Semigroup[E]): Validated[E, C] = check1(a).withEither(_.flatMap(b => check2(b).toEither)) -} ``` diff --git a/src/pages/foldable-traverse/traverse-cats.md b/src/pages/foldable-traverse/traverse-cats.md index 9de8a6b5..21d456ad 100644 --- a/src/pages/foldable-traverse/traverse-cats.md +++ b/src/pages/foldable-traverse/traverse-cats.md @@ -10,14 +10,13 @@ Here's the abbreviated definition: ```scala package cats -trait Traverse[F[_]] { +trait Traverse[F[_]]: def traverse[G[_]: Applicative, A, B] (inputs: F[A])(func: A => G[B]): G[F[B]] def sequence[G[_]: Applicative, B] (inputs: F[G[B]]): G[F[B]] = traverse(inputs)(identity) -} ``` Cats provides instances of `Traverse` diff --git a/src/pages/foldable-traverse/traverse.md b/src/pages/foldable-traverse/traverse.md index 025402ba..ba5518e9 100644 --- a/src/pages/foldable-traverse/traverse.md +++ b/src/pages/foldable-traverse/traverse.md @@ -113,12 +113,11 @@ that assumes we're starting with a `List[Future[B]]` and don't need to provide an identity function: ```scala -object Future { +object Future: def sequence[B](futures: List[Future[B]]): Future[List[B]] = traverse(futures)(identity) // etc... -} ``` In this case the intuitive understanding is even simpler: diff --git a/src/pages/monad-transformers/index.md b/src/pages/monad-transformers/index.md index ab47b64c..fd4ae570 100644 --- a/src/pages/monad-transformers/index.md +++ b/src/pages/monad-transformers/index.md @@ -297,9 +297,8 @@ However, we can't define this in one line because `EitherT` has three type parameters: ```scala -case class EitherT[F[_], E, A](stack: F[Either[E, A]]) { +case class EitherT[F[_], E, A](stack: F[Either[E, A]]): // etc... -} ``` The three type parameters are as follows: diff --git a/src/pages/monads/either.md b/src/pages/monads/either.md index 72a27eba..69738d14 100644 --- a/src/pages/monads/either.md +++ b/src/pages/monads/either.md @@ -235,13 +235,13 @@ Another approach is to define an algebraic data type to represent errors that may occur in our program: ```scala mdoc:silent -object wrapper { - enum LoginError { +object wrapper: + enum LoginError: case UserNotFound(username: String) case PasswordIncorrect(username: String) case UnexpectedError - } -}; import wrapper.* + +import wrapper.* import LoginError.* ``` diff --git a/src/pages/monads/index.md b/src/pages/monads/index.md index 3cdf61a7..edb7958a 100644 --- a/src/pages/monads/index.md +++ b/src/pages/monads/index.md @@ -257,11 +257,10 @@ Here is a simplified version of the `Monad` type class in Cats: ```scala mdoc:silent -trait Monad[F[_]] { +trait Monad[F[_]]: def pure[A](value: A): F[A] def flatMap[A, B](value: F[A])(func: A => F[B]): F[B] -} ```
@@ -302,14 +301,13 @@ using the existing methods, `flatMap` and `pure`: ```scala mdoc:silent:reset-object -trait Monad[F[_]] { +trait Monad[F[_]]: def pure[A](a: A): F[A] def flatMap[A, B](value: F[A])(func: A => F[B]): F[B] def map[A, B](value: F[A])(func: A => B): F[B] = ??? -} ``` Try defining `map` yourself now. @@ -322,14 +320,13 @@ Given the tools available there's only one thing we can do: call `flatMap`: ```scala -trait Monad[F[_]] { +trait Monad[F[_]]: def pure[A](value: A): F[A] def flatMap[A, B](value: F[A])(func: A => F[B]): F[B] def map[A, B](value: F[A])(func: A => B): F[B] = flatMap(value)(a => ???) -} ``` We need a function of type `A => F[B]` as the second parameter. @@ -341,13 +338,12 @@ Combining these gives us our result: ```scala mdoc:invisible:reset-object ``` ```scala mdoc -trait Monad[F[_]] { +trait Monad[F[_]]: def pure[A](value: A): F[A] def flatMap[A, B](value: F[A])(func: A => F[B]): F[B] def map[A, B](value: F[A])(func: A => B): F[B] = flatMap(value)(a => pure(func(a))) -} ```
diff --git a/src/pages/monads/monad-error.md b/src/pages/monads/monad-error.md index 73591ac2..a657f437 100644 --- a/src/pages/monads/monad-error.md +++ b/src/pages/monads/monad-error.md @@ -28,7 +28,7 @@ of the definition of `MonadError`: ```scala package cats -trait MonadError[F[_], E] extends Monad[F] { +trait MonadError[F[_], E] extends Monad[F]: // Lift an error into the `F` context: def raiseError[A](e: E): F[A] @@ -41,7 +41,6 @@ trait MonadError[F[_], E] extends Monad[F] { // Test an instance of `F`, // failing if the predicate is not satisfied: def ensure[A](fa: F[A])(e: E)(f: A => Boolean): F[A] -} ``` `MonadError` is defined in terms of two type parameters: From 3a34cedb1d6478408e87c5846447af37b6ef6304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 21:44:35 -0600 Subject: [PATCH 28/33] =?UTF-8?q?Address=20some=20implicit=20=E2=86=92=20g?= =?UTF-8?q?iven/using=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/functors/cats.md | 4 ++-- src/pages/functors/contravariant-invariant.md | 2 +- src/pages/monoids/cats.md | 2 +- src/pages/type-classes/implicits.md | 11 +++++------ src/pages/type-classes/printable.md | 7 +++---- src/pages/type-classes/summary.md | 4 ++-- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/pages/functors/cats.md b/src/pages/functors/cats.md index 06d056e6..a3cd5cc2 100644 --- a/src/pages/functors/cats.md +++ b/src/pages/functors/cats.md @@ -152,7 +152,7 @@ given optionFunctor: Functor[Option] with Sometimes we need to inject dependencies into our instances. For example, if we had to define a custom `Functor` for `Future` (another hypothetical example---Cats provides one in `cats.instances.future`) -we would need to account for the implicit `ExecutionContext` parameter on `future.map`. +we would need to account for the using clause `ExecutionContext` parameter on `future.map`. We can't add extra parameters to `functor.map` so we have to account for the dependency when we create the instance: @@ -167,7 +167,7 @@ given futureFunctor(using ec: ExecutionContext): Functor[Future] with Whenever we summon a `Functor` for `Future`, either directly using `Functor.apply` or indirectly via the `map` extension method, -the compiler will locate `futureFunctor` by implicit resolution +the compiler will locate `futureFunctor` by given instance resolution and recursively search for an `ExecutionContext` at the call site. This is what the expansion might look like: diff --git a/src/pages/functors/contravariant-invariant.md b/src/pages/functors/contravariant-invariant.md index 72396805..5831dbe2 100644 --- a/src/pages/functors/contravariant-invariant.md +++ b/src/pages/functors/contravariant-invariant.md @@ -123,7 +123,7 @@ format(true) Now define an instance of `Printable` for the following `Box` case class. -You'll need to write this as an `implicit def` +You'll need to write this as a `given` instance as described in Section [@sec:type-classes:recursive-implicits]: ```scala mdoc:silent diff --git a/src/pages/monoids/cats.md b/src/pages/monoids/cats.md index d74c9915..40dec61f 100644 --- a/src/pages/monoids/cats.md +++ b/src/pages/monoids/cats.md @@ -45,7 +45,7 @@ are defined directly in the [`cats`][cats.package] package. the companion object has an `apply` method that returns the type class instance for a particular type. For example, if we want the monoid instance for `String`, -and we have the correct implicits in scope, +and we have the correct given instances in scope, we can write the following: ```scala mdoc:silent diff --git a/src/pages/type-classes/implicits.md b/src/pages/type-classes/implicits.md index b0eace35..0a0f5a29 100644 --- a/src/pages/type-classes/implicits.md +++ b/src/pages/type-classes/implicits.md @@ -84,8 +84,7 @@ object Json: w.write(value) ``` ```scala mdoc:fail -given secondStringWriter: JsonWriter[String] = - JsonWriterInstances.stringWriter +given secondStringWriter: JsonWriter[String] = stringWriter Json.toJson("A string") ``` @@ -183,15 +182,15 @@ When the compiler sees an expression like this: Json.toJson(Option("A string")) ``` -it searches for an implicit `JsonWriter[Option[String]]`. -It finds the implicit method for `JsonWriter[Option[A]]`: +it searches for a given instance `JsonWriter[Option[String]]`. +It finds the given instance for `JsonWriter[Option[A]]`: ```scala mdoc:silent Json.toJson(Option("A string"))(using optionWriter[String]) ``` and recursively searches for a `JsonWriter[String]` -to use as the parameter to `optionWriter`: +for the using clause to `optionWriter`: ```scala mdoc:silent Json.toJson(Option("A string"))(using optionWriter(using stringWriter)) @@ -216,7 +215,7 @@ fill in the parameters during given instance resolution. `given` methods with non-`using` parameters form a different Scala pattern called an *implicit conversion*. This is also different from the previous section on `Interface Syntax`, -because in that case the `JsonWriter` is an implicit class with extension methods. +because in that case the `JsonWriter` has extension methods. Implicit conversion is an older programming pattern that is frowned upon in modern Scala code. Fortunately, the compiler will warn you when you do this. diff --git a/src/pages/type-classes/printable.md b/src/pages/type-classes/printable.md index 06e0b8f1..1e86b214 100644 --- a/src/pages/type-classes/printable.md +++ b/src/pages/type-classes/printable.md @@ -32,8 +32,7 @@ trait Printable[A]: def format(value: A): String ``` -Then we define some default *instances* of `Printable` -and package them in `PrintableInstances`: +Then we define some default *instances* of `Printable`: ```scala mdoc:silent given stringPrintable: Printable[String] with @@ -129,10 +128,10 @@ by defining some extension methods to provide better syntax: 2. Define the following extension methods: - - `format` using an implicit `Printable[A]` + - `format` using a `Printable[A]` and returns a `String` representation of the wrapped `A`; - - `print` using an implicit `Printable[A]` and returns `Unit`. + - `print` using a `Printable[A]` and returns `Unit`. It prints the wrapped `A` to the console. 3. Use the extension methods to print the example `Cat` diff --git a/src/pages/type-classes/summary.md b/src/pages/type-classes/summary.md index 2f61c00c..9920935d 100644 --- a/src/pages/type-classes/summary.md +++ b/src/pages/type-classes/summary.md @@ -8,9 +8,9 @@ We saw the components that make up a type class: - A `trait`, which is the type class -- Type class instances, which are implicit values. +- Type class instances, which are given instances. -- Type class usage, which uses implicit parameters. +- Type class usage, which utilizes using clauses. We have also seen the general patterns in Cats type classes: From 34e4b7639aea009db30dc33cc98db37258f9255f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 22:08:50 -0600 Subject: [PATCH 29/33] Preface Scala 3 tweak --- src/pages/preface/conventions.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/preface/conventions.md b/src/pages/preface/conventions.md index f98ed4b1..9960e586 100644 --- a/src/pages/preface/conventions.md +++ b/src/pages/preface/conventions.md @@ -25,9 +25,8 @@ Source code blocks are written as follows. Syntax is highlighted appropriately where applicable: ```scala mdoc:silent -object MyApp extends App { +object MyApp extends App: println("Hello world!") // Print a fine message to the user! -} ``` Most code passes through [mdoc][link-mdoc] to ensure it compiles. From fb80a9c1618ba90c603e04fbd1a0e7be0e06a30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Sat, 11 Feb 2023 22:21:14 -0600 Subject: [PATCH 30/33] That does work due to package --- src/pages/type-classes/cats.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/type-classes/cats.md b/src/pages/type-classes/cats.md index 58d1a257..5f396e12 100644 --- a/src/pages/type-classes/cats.md +++ b/src/pages/type-classes/cats.md @@ -34,13 +34,9 @@ The companion object of every Cats type class has an `apply` method that locates an instance for any type we specify: ```scala mdoc -val showInt = Show.apply[Int] +given showInt: Show[Int] = Show.apply[Int] ``` -Oops---that didn't work! -The `apply` method uses *implicits* to look up individual instances, -so we'll have to bring some instances into scope. - ### Importing Default Instances {#sec:importing-default-instances} The [`cats.instances`][cats.instances] package From 1fa5dec14ba4f729f1b9f6a8cdd1853df9e0e3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Fri, 17 Feb 2023 20:18:39 -0600 Subject: [PATCH 31/33] what-is-fp: adjust code --- src/pages/intro/what-is-fp.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/intro/what-is-fp.md b/src/pages/intro/what-is-fp.md index 1a735e20..43fc434f 100644 --- a/src/pages/intro/what-is-fp.md +++ b/src/pages/intro/what-is-fp.md @@ -30,7 +30,7 @@ In my view functional programming is not about immutability, or keeping to "the ```scala mdoc:silent def sum(numbers: List[Int]): Int = { var total = 0 - numbers.foreach(x => total = total + x) + numbers foreach { x => total = total + x } total } ``` @@ -50,7 +50,7 @@ val it2 = Iterator(1, 2, 3, 4) ``` ```scala mdoc -it.zip(it2).next() +(it zip it2).next() ``` However if we pass the same generator twice we get a surprising result. @@ -60,7 +60,7 @@ val it3 = Iterator(1, 2, 3, 4) ``` ```scala mdoc -it3.zip(it3).next() +(it3 zip it3).next() ``` The usual functional programming solution is to avoid mutable state but we can envisage other possibilities. For example, an [effect tracking system][effect-system] would allow us to avoid combining two generators that use the same memory region. These systems are still research projects, however. From 3847a8cebd17ba22f053952651643a868191b38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Fri, 17 Feb 2023 20:42:58 -0600 Subject: [PATCH 32/33] anatomy: further refactor implicit to contextual abstractions --- src/pages/type-classes/anatomy.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/type-classes/anatomy.md b/src/pages/type-classes/anatomy.md index d892e252..1e019730 100644 --- a/src/pages/type-classes/anatomy.md +++ b/src/pages/type-classes/anatomy.md @@ -141,26 +141,25 @@ for the using clauses and fills them in for us: Person("Dave", "dave@example.com").toJson(using personWriter) ``` -**The *implicitly* Method** +**The *summon* Method** The Scala standard library provides -a generic type class interface called `implicitly`. +a generic type class interface called `summon`. Its definition is very simple: ```scala -def implicitly[A](using value: A): A = - value +def summon[A](using value: A): A = value ``` -We can use `implicitly` to summon any value from implicit scope. -We provide the type we want and `implicitly` does the rest: +We can use `summon` to summon any value from the contextual abstractions scope. +We provide the type we want and `summon` does the rest: ```scala mdoc -implicitly[JsonWriter[String]] +summon[JsonWriter[String]] ``` Most type classes in Cats provide other means to summon instances. -However, `implicitly` is a good fallback for debugging purposes. -We can insert a call to `implicitly` within the general flow of our code +However, `summon` is a good fallback for debugging purposes. +We can insert a call to `summon` within the general flow of our code to ensure the compiler can find an instance of a type class and ensure that there are no ambiguous given instances errors. From c14b942f84fed4988d87580aa74e850fb5870f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Heredia=20Montiel?= Date: Fri, 17 Feb 2023 21:38:33 -0600 Subject: [PATCH 33/33] implicits: refactor implicits --- src/pages/type-classes/implicits.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/pages/type-classes/implicits.md b/src/pages/type-classes/implicits.md index 0a0f5a29..319beffc 100644 --- a/src/pages/type-classes/implicits.md +++ b/src/pages/type-classes/implicits.md @@ -1,4 +1,4 @@ -## Working with Given Instances and Using Clauses +## Working with Contextual Abstractions ```scala mdoc:invisible // Forward definitions @@ -36,7 +36,7 @@ Working with type classes in Scala means working with given instances and using clauses. There are a few rules we need to know to do this effectively. -### Implicit Scope +### Contextual Abstractions Scope As we saw above, the compiler searches for candidate type class instances by type. @@ -49,10 +49,10 @@ Json.toJson("A string!") ``` The places where the compiler searches for candidate instances -is known as the *implicit scope*. -The implicit scope applies at the call site; +is known as the *contextual abstractions scope*. +The contextual abstractions scope applies at the call site; that is the point where we call a method with a using clause. -The implicit scope which roughly consists of: +The contextual abstractions scope which roughly consists of: - local or inherited definitions; @@ -101,14 +101,14 @@ For our purposes, we can package type class instances in roughly five ways: With option 1 and 2 we bring given instances into scope by `importing` them explicitly. With option 3 we bring them into scope with inheritance. -With options 4 and 5 instances are *always* in implicit scope, +With options 4 and 5 instances are *always* in the contextual abstractions scope, regardless of where we try to use them. It is conventional to put type class instances in a companion object (option 4 and 5 above) if there is only one sensible implementation, or at least one implementation that is widely accepted as the default. This makes type class instances easier to use -as no import is required to bring them into the implicit scope. +as no import is required to bring them into the contextual abstractions scope. [^implicit-search]: If you're interested in the finer rules of implicit resolution in Scala, start by taking a look at [this Stack Overflow post on implicit scope][link-so-implicit-scope] @@ -203,7 +203,9 @@ a combination that creates a type class instance of the correct overall type.
-*Implicit Conversions* +*Contextual Implicit Conversions* + +// TODO: https://docs.scala-lang.org/scala3/reference/contextual/conversions.html When you create a type class instance constructor using an `given`, @@ -230,11 +232,6 @@ trait JsonWriter[A]: ```scala modc:warn given optionWriter[A](writer: JsonWriter[A]): JsonWriter[Option[A]] = ??? -// warning: implicit conversion method foo should be enabled -// by making the implicit value scala.language.implicitConversions visible. -// This can be achieved by adding the import clause 'import scala.language.implicitConversions' -// or by setting the compiler option -language:implicitConversions. -// See the Scaladoc for value scala.language.implicitConversions for a discussion -// why the feature should be explicitly enabled. +// TODO: Fix formatting ```