diff --git a/build.sbt b/build.sbt
index 60c4b770..57cb8c5f 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
@@ -14,11 +14,14 @@ enablePlugins(MdocPlugin)
mdocIn := sourceDirectory.value / "pages"
mdocOut := target.value / "pages"
-val catsVersion = "2.7.0"
+scalacOptions ++= Seq(
+ "-explain", // Better diagnostics
+ "-Ykind-projector:underscores" // In-lieu of kind-projector
+)
-libraryDependencies ++= Seq("org.typelevel" %% "cats-core" % catsVersion)
+val catsVersion = "2.9.0"
-addCompilerPlugin("org.typelevel" % "kind-projector" % "0.13.2" cross CrossVersion.full)
+libraryDependencies ++= Seq("org.typelevel" %% "cats-core" % catsVersion)
mdocVariables := Map(
"SCALA_VERSION" -> scalaVersion.value,
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/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,
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
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")
diff --git a/src/pages/adt/scala.md b/src/pages/adt/scala.md
index 95b1593c..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,13 +35,12 @@ 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`. 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
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/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..3f9191a0 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"))
@@ -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:
@@ -115,13 +114,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))
@@ -160,7 +157,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 09bba7d1..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,
@@ -34,7 +33,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 +58,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 +97,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 +166,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,
@@ -185,7 +184,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]]
@@ -196,7 +195,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 52d214db..64731507 100644
--- a/src/pages/case-studies/crdt/abstraction.md
+++ b/src/pages/case-studies/crdt/abstraction.md
@@ -27,48 +27,38 @@ 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 {
- 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]]{
- 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)
- (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 {
+object GCounter:
def apply[F[_,_], K, V]
- (implicit counter: GCounter[F, K, V]) =
+ (using counter: GCounter[F, K, V]) =
counter
-}
```
Try defining an instance of this type class for `Map`.
@@ -83,34 +73,32 @@ 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
-
-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] = {
- val total = map.getOrElse(key, m.empty) |+| value
- map + (key -> total)
- }
+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)
+ (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] =
- 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])
- (implicit m: CommutativeMonoid[V]): V =
- map.values.toList.combineAll
- }
+ def total(map: Map[K, V])
+ (using m: CommutativeMonoid[V]): V =
+ map.values.toList.combineAll
```
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)
@@ -120,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
@@ -139,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]
@@ -148,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`.
@@ -160,21 +147,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
```
@@ -182,19 +167,19 @@ 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)
- (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)
}
```
@@ -205,22 +190,19 @@ instances of `KeyValueStore` and `CommutativeMonoid`
using an `implicit def`:
```scala mdoc:silent
-implicit def gcounterInstance[F[_,_], K, V]
- (implicit kvs: KeyValueStore[F], km: CommutativeMonoid[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)
- (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
- }
```
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 1b4fb4d8..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,32 +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 {
- 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]]{
- 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.*
```
@@ -196,25 +189,24 @@ 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]) {
+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/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 671e622c..4cd99f06 100644
--- a/src/pages/case-studies/parser/applicative.md
+++ b/src/pages/case-studies/parser/applicative.md
@@ -15,16 +15,14 @@ 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 {
+ 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.
@@ -107,8 +105,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)) =
@@ -145,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:
@@ -168,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.
@@ -198,7 +194,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 +254,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/parser/error-handling.md b/src/pages/case-studies/parser/error-handling.md
index fb6c4b60..3abf3bec 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)
+}
~~~
@@ -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 d6ed4990..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 =
@@ -51,20 +47,16 @@ 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)
}
-}
-
-object Parser {
-
+object Parser:
def string(literal: String): Parser =
Parser { input =>
if(input.startsWith(literal))
@@ -72,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`.
@@ -115,18 +105,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/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/case-studies/testing/index.md b/src/pages/case-studies/testing/index.md
index f1d3ba76..2843fa58 100644
--- a/src/pages/case-studies/testing/index.md
+++ b/src/pages/case-studies/testing/index.md
@@ -15,24 +15,22 @@ 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
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) {
+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`.
@@ -53,7 +50,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)
@@ -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)
-}
```
@@ -268,44 +252,40 @@ 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[_]] {
+trait UptimeClient[F[_]]:
def getUptime(hostname: String): F[Int]
-}
```
```scala mdoc:silent
import cats.Applicative
-import cats.syntax.functor._ // for map
+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)
-}
```
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[_]] {
+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`
@@ -326,29 +306,25 @@ 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[_]] {
+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 a9d89c3a..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?
@@ -83,8 +81,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,17 +129,17 @@ 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
-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)
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
@@ -150,14 +148,13 @@ final case class CheckF[E, A](func: A => Either[E, A]) {
case (Right(_), Right(_)) => a.asRight
}
}
-}
```
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,14 +190,14 @@ 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._
-final case class CheckF[E, A](func: A => Either[E, A]) {
+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)
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
@@ -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] =
@@ -235,19 +231,19 @@ 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._
+sealed trait Check[E, A]:
+ import Check.*
def and(that: Check[E, A]): Check[E, A] =
And(this, that)
- def apply(a: A)(implicit s: Semigroup[E]): Either[E, A] =
- this match {
+ def apply(a: A)(using s: Semigroup[E]): Either[E, A] =
+ this match
case Pure(func) =>
func(a)
@@ -258,9 +254,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 +266,6 @@ object Check {
def pure[E, A](f: A => Either[E, A]): Check[E, A] =
Pure(f)
-}
```
Let's see an example:
@@ -332,33 +327,31 @@ 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._
+sealed trait Check[E, A]:
+ import Check.*
def and(that: Check[E, A]): Check[E, A] =
And(this, that)
- def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] =
- this match {
+ def apply(a: A)(using s: Semigroup[E]): Validated[E, A] =
+ 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]
-}
```
@@ -375,14 +368,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._
+sealed trait Check[E, A]:
+ import Check.*
def and(that: Check[E, A]): Check[E, A] =
And(this, that)
@@ -390,8 +383,8 @@ 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] =
- this match {
+ def apply(a: A)(using s: Semigroup[E]): Validated[E, A] =
+ this match
case Pure(func) =>
func(a)
@@ -407,9 +400,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 +413,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 91d0421e..a26e3d8b 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`
@@ -125,15 +125,14 @@ Like `apply`, the method must accept an implicit `Semigroup`:
import cats.Semigroup
import cats.data.Validated
-sealed trait Predicate[E, A] {
- def run(implicit s: Semigroup[E]): A => Either[E, A] =
+sealed trait Predicate[E, A]:
+ def run(using s: Semigroup[E]): A => Either[E, A] =
(a: A) => this(a).toEither
def apply(a: A): Validated[E, A] =
??? // etc...
// other methods...
-}
```
@@ -172,13 +171,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._
+sealed trait Predicate[E, A]:
+ import Predicate.*
def and(that: Predicate[E, A]): Predicate[E, A] =
And(this, that)
@@ -186,11 +185,11 @@ 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] =
- this match {
+ def apply(a: A)(using s: Semigroup[E]): Validated[E, A] =
+ this match
case Pure(func) =>
func(a)
@@ -206,10 +205,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 +225,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
@@ -237,7 +236,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
@@ -330,7 +329,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..c911044b 100644
--- a/src/pages/case-studies/validation/map.md
+++ b/src/pages/case-studies/validation/map.md
@@ -89,21 +89,21 @@ 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
-sealed trait Predicate[E, A] {
+sealed trait Predicate[E, A]:
def and(that: Predicate[E, A]): Predicate[E, A] =
And(this, that)
def or(that: Predicate[E, A]): Predicate[E, A] =
Or(this, that)
- def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] =
- this match {
+ def apply(a: A)(using s: Semigroup[E]): Validated[E, A] =
+ 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] =
???
-}
```
@@ -158,11 +156,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._
+sealed trait Predicate[E, A]:
+ import Predicate.*
+ import Validated.*
def and(that: Predicate[E, A]): Predicate[E, A] =
And(this, that)
@@ -170,8 +168,8 @@ 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] =
- this match {
+ def apply(a: A)(using s: Semigroup[E]): Validated[E, A] =
+ 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
@@ -208,35 +205,32 @@ import cats.data.Validated
```
```scala mdoc:silent
-sealed trait Check[E, A, B] {
- import Check._
+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)
-}
-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)(implicit s: Semigroup[E]): Validated[E, 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)(implicit s: Semigroup[E]): Validated[E, 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)
-}
-
```
@@ -288,22 +282,20 @@ import cats.data.Validated
```
```scala mdoc:silent
-sealed trait Check[E, A, B] {
- def apply(in: A)(implicit s: Semigroup[E]): Validated[E, 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)(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))
-}
// other data types...
```
@@ -325,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!
@@ -341,20 +332,18 @@ import cats.Semigroup
import cats.data.Validated
```
```scala mdoc:silent
-sealed trait Check[E, A, B] {
- def apply(in: A)(implicit s: Semigroup[E]): Validated[E, 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)(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))
-}
```
@@ -373,10 +362,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`,
@@ -385,9 +374,9 @@ a `Predicate.apply` method to create
a `Predicate` from a function:
```scala mdoc:silent
-sealed trait Predicate[E, A] {
- import Predicate._
- import Validated._
+sealed trait Predicate[E, A]:
+ import Predicate.*
+ import Validated.*
def and(that: Predicate[E, A]): Predicate[E, A] =
And(this, that)
@@ -395,8 +384,8 @@ 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] =
- this match {
+ def apply(a: A)(using s: Semigroup[E]): Validated[E, A] =
+ this match
case Pure(func) =>
func(a)
@@ -412,10 +401,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 +420,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`.
@@ -444,14 +431,14 @@ 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._
+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)
@@ -461,51 +448,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)
- (implicit s: Semigroup[E]): Validated[E, C] =
+ (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)
- (implicit s: Semigroup[E]): Validated[E, C] =
+ (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)
- (implicit s: Semigroup[E]): Validated[E, C] =
+ (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)
- (implicit s: Semigroup[E]): Validated[E, B] =
+ (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)
- (implicit s: Semigroup[E]): Validated[E, 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 +499,6 @@ object Check {
def apply[E, A, B]
(func: A => Validated[E, B]): Check[E, A, B] =
Pure(func)
-}
```
@@ -589,8 +574,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`:
@@ -614,14 +599,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 +615,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(_ + "@" + _)
}
@@ -647,7 +632,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/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 576aef44..42307f42 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,10 +156,10 @@ 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
+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 b4a78c72..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`
@@ -28,8 +27,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 +43,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,12 +69,12 @@ 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
-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/foldable-traverse/traverse.md b/src/pages/foldable-traverse/traverse.md
index 500abc1d..ba5518e9 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(
@@ -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:
@@ -157,8 +156,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 +180,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 +222,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 +262,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 +296,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 +306,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 3ae4835c..a3cd5cc2 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
@@ -83,11 +83,11 @@ 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
-import cats.instances.list._ // for Functor
+import cats.instances.option.* // for Functor
+import cats.instances.list.* // for Functor
```
```scala mdoc
@@ -101,11 +101,10 @@ 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)
- (implicit functor: Functor[F]): F[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:
@@ -153,35 +144,30 @@ 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.
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:
```scala mdoc:silent
import scala.concurrent.{Future, ExecutionContext}
-implicit def futureFunctor
- (implicit 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`,
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:
@@ -190,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
@@ -202,12 +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
-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]
```
@@ -218,17 +201,15 @@ with the same pattern of `Branch` and `Leaf` nodes:
```scala mdoc:silent
import cats.Functor
+import Tree.{Branch, Leaf}
-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`:
@@ -243,13 +224,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 71341612..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
@@ -30,7 +28,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 +45,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
@@ -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`
@@ -95,11 +92,11 @@ 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 |+|
-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..5831dbe2 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,14 +47,13 @@ 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)(implicit p: Printable[A]): String =
+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,18 +88,16 @@ 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)(implicit p: Printable[A]): String =
+def format[A](value: A)(using p: Printable[A]): String =
p.format(value)
```
@@ -113,17 +107,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}'"
- }
-
-implicit val booleanPrintable: Printable[Boolean] =
- new Printable[Boolean] {
- def format(value: Boolean): String =
- if(value) "yes" else "no"
- }
+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"
```
```scala mdoc
@@ -133,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
@@ -147,7 +137,7 @@ create your instance from an
existing instance using `contramap`.
```scala mdoc:invisible
-implicit def boxPrintable[A](implicit p: Printable[A]): Printable[Box[A]] =
+given boxPrintable[A](using p: Printable[A]): Printable[Box[A]] =
p.contramap[Box[A]](_.value)
```
@@ -171,63 +161,49 @@ 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)(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}'"
- }
-
-implicit val booleanPrintable: Printable[Boolean] =
- new Printable[Boolean] {
- def format(value: Boolean): String =
- if(value) "yes" else "no"
- }
+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"
+
final case class Box[A](value: A)
```
```scala mdoc:silent
-implicit def boxPrintable[A](implicit p: Printable[A]): Printable[Box[A]] =
+given boxPrintable[A](using p: Printable[A]): Printable[Box[A]] =
p.contramap[Box[A]](_.value)
```
@@ -257,36 +233,32 @@ 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
-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 +275,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)
```
@@ -344,39 +314,35 @@ 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
-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 +355,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 +372,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]] =
+given boxCodec[A](using c: Codec[A]): Codec[Box[A]] =
c.imap[Box[A]](Box(_), _.value)
```
diff --git a/src/pages/functors/index.md b/src/pages/functors/index.md
index 267b0a30..87704802 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).
@@ -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)
}
```
@@ -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
@@ -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 57b3ad93..cb4a9599 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
@@ -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
@@ -123,7 +121,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/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.
diff --git a/src/pages/links.md b/src/pages/links.md
index 48909519..bd3baf76 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
@@ -122,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/monad-transformers/index.md b/src/pages/monad-transformers/index.md
index cf92a0ce..fd4ae570 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
@@ -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:
@@ -325,10 +324,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 +350,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 +451,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
@@ -482,10 +481,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]] = {
@@ -589,7 +587,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 +619,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 +662,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 +685,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 e1c005ce..21cf1411 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)
@@ -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)
@@ -167,28 +167,26 @@ the non-tail-recursive solution falls out:
```scala mdoc:silent
import cats.Monad
-implicit val treeMonad = new Monad[Tree] {
+given treeMonad: Monad[Tree] with
def pure[A](value: A): Tree[A] =
Leaf(value)
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] =
- 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)
+ }
```
The solution above is perfectly fine for this exercise.
@@ -219,26 +217,26 @@ def leaf[A](value: A): Tree[A] =
import cats.Monad
import scala.annotation.tailrec
-implicit val treeMonad = new Monad[Tree] {
+given treeMonad: Monad[Tree] with
def pure[A](value: A): Tree[A] =
Leaf(value)
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]]): 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]]],
closed: List[Option[Tree[B]]]): List[Tree[B]] =
- open match {
+ open match
case Branch(l, r) :: next =>
loop(l :: r :: next, None :: closed)
@@ -255,19 +253,18 @@ implicit val treeMonad = new Monad[Tree] {
branch(left, right) :: tail
}
}
- }
+ end match
loop(List(func(arg)), Nil).head
}
-}
```
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 8990016e..69738d14 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
@@ -235,17 +235,15 @@ Another approach is to define an algebraic data type
to represent errors that may occur in our program:
```scala mdoc:silent
-object wrapper {
- sealed trait LoginError extends Product with Serializable
+object wrapper:
+ enum LoginError:
+ case UserNotFound(username: String)
+ case PasswordIncorrect(username: String)
+ case UnexpectedError
- final case class UserNotFound(username: String)
- extends LoginError
+import wrapper.*
- final case class PasswordIncorrect(username: String)
- extends LoginError
-
- case object UnexpectedError extends LoginError
-}; import wrapper._
+import LoginError.*
```
```scala mdoc:silent
@@ -263,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")
@@ -272,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/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/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 ba5c46bc..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:
@@ -55,7 +54,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 +125,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 +164,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")
@@ -180,14 +179,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]
```
@@ -206,10 +205,10 @@ 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)(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/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..32616453 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
@@ -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,16 +340,15 @@ 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
+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 8c430d3e..40dec61f 100644
--- a/src/pages/monoids/cats.md
+++ b/src/pages/monoids/cats.md
@@ -45,12 +45,12 @@ 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
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,9 +185,9 @@ 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])(implicit monoid: Monoid[A]): A =
+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
@@ -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..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,
@@ -114,13 +113,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)
}
@@ -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 {
- def apply[A](implicit monoid: Monoid[A]) =
+object Monoid:
+ def apply[A](using monoid: Monoid[A]) =
monoid
-}
```
@@ -204,46 +198,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.
@@ -259,11 +245,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
@@ -285,11 +269,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,
@@ -301,11 +283,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
```
diff --git a/src/pages/monoids/summary.md b/src/pages/monoids/summary.md
index bee25e28..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)
@@ -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/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.
diff --git a/src/pages/preface/versions.md b/src/pages/preface/versions.md
index 9da7f8e5..9aefbc41 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"
)
```
@@ -32,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.
diff --git a/src/pages/type-classes/anatomy.md b/src/pages/type-classes/anatomy.md
index f61cb34a..1e019730 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.
@@ -27,16 +27,15 @@ 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
// 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,
@@ -53,40 +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 {
- 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)
- ))
- }
-
- // etc...
-}
-```
+given stringWriter: JsonWriter[String] with
+ def write(value: String): Json =
+ Json.JsString(value)
+
+given personWriter: JsonWriter[Person] with
+ def write(value: Person): Json =
+ Json.JsObject(Map(
+ "name" -> Json.JsString(value.name),
+ "email" -> Json.JsString(value.email)
+ ))
-These are known as implicit values.
+// etc...
+```
+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.
@@ -98,30 +90,25 @@ 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 {
- def toJson[A](value: A)(implicit w: JsonWriter[A]): 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._
-```
-
```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:
```scala mdoc:silent
-Json.toJson(Person("Dave", "dave@example.com"))(personWriter)
+Json.toJson(Person("Dave", "dave@example.com"))(using personWriter)
```
**Interface Syntax**
@@ -135,55 +122,44 @@ 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(implicit w: JsonWriter[A]): Json =
- w.write(value)
- }
-}
+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._
-import JsonSyntax._
-```
-
```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(personWriter)
+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](implicit 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
-import JsonWriterInstances._
-
-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 implicit errors.
+and ensure that there are no ambiguous given instances errors.
diff --git a/src/pages/type-classes/cats.md b/src/pages/type-classes/cats.md
index 6e53501d..5f396e12 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
@@ -35,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
@@ -63,8 +58,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]
@@ -89,7 +84,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
@@ -108,9 +103,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.
@@ -123,11 +118,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
@@ -139,7 +132,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] =
???
@@ -147,7 +140,6 @@ object Show {
// Create a `Show` instance from a `toString` method:
def fromToString[A]: Show[A] =
???
-}
```
These allow us to quickly construct instances
@@ -158,7 +150,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.")
```
@@ -181,9 +173,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:
@@ -196,7 +188,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..131a270b 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],
@@ -58,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]
```
@@ -82,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
@@ -103,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:
@@ -132,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
@@ -147,11 +146,11 @@ 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
-implicit val dateEq: Eq[Date] =
+given dateEq: Eq[Date] =
Eq.instance[Date] { (date1, date2) =>
date1.getTime === date2.getTime
}
@@ -193,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:
@@ -206,10 +205,10 @@ 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
-implicit val catEqual: Eq[Cat] =
+given catEqual: Eq[Cat] =
Eq.instance[Cat] { (cat1, cat2) =>
(cat1.name === cat2.name ) &&
(cat1.age === cat2.age ) &&
@@ -228,7 +227,7 @@ cat1 =!= cat2
```
```scala mdoc:silent
-import cats.instances.option._ // for Eq
+import cats.instances.option.* // for Eq
```
```scala mdoc
diff --git a/src/pages/type-classes/implicits.md b/src/pages/type-classes/implicits.md
index 1f179e0f..319beffc 100644
--- a/src/pages/type-classes/implicits.md
+++ b/src/pages/type-classes/implicits.md
@@ -1,67 +1,42 @@
-## Working with Implicits
+## Working with Contextual Abstractions
```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
-trait JsonWriter[A] {
+trait JsonWriter[A]:
def write(value: A): Json
-}
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)
- ))
- }
-
- // etc...
-}
-
-import JsonWriterInstances._
-
-object Json {
- def toJson[A](value: A)(implicit w: JsonWriter[A]): Json =
+given stringWriter: JsonWriter[String] with
+ def write(value: String): Json =
+ Json.JsString(value)
+
+given personWriter: JsonWriter[Person] with
+ def write(value: Person): Json =
+ Json.JsObject(Map(
+ "name" -> Json.JsString(value.name),
+ "email" -> Json.JsString(value.email)
+ ))
+
+// etc...
+
+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
+### Contextual Abstractions Scope
As we saw above, the compiler searches
for candidate type class instances by type.
@@ -74,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;
-that is the point where we call a method with an implicit parameter.
-The implicit scope which roughly consists of:
+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 contextual abstractions scope which roughly consists of:
- local or inherited definitions;
@@ -87,109 +62,96 @@ 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
-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
-trait JsonWriter[A] {
+trait JsonWriter[A]:
def write(value: A): Json
-}
-
-object JsonWriterInstances {
- implicit val stringWriter: JsonWriter[String] =
- new JsonWriter[String] {
- def write(value: String): Json =
- JsString(value)
- }
-}
-
-object Json {
- def toJson[A](value: A)(implicit w: JsonWriter[A]): Json =
+
+given stringWriter: JsonWriter[String] with
+ def write(value: String): Json =
+ Json.JsString(value)
+
+object Json:
+ def toJson[A](value: A)(using w: JsonWriter[A]): Json =
w.write(value)
-}
```
```scala mdoc:fail
-implicit val writer1: JsonWriter[String] =
- JsonWriterInstances.stringWriter
-
-implicit val writer2: JsonWriter[String] =
- JsonWriterInstances.stringWriter
+given secondStringWriter: JsonWriter[String] = 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 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 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
-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]
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
-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...
```
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,66 +163,61 @@ 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]
- (implicit 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._
-```
```scala mdoc:silent
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"))(optionWriter[String])
+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"))(optionWriter(stringWriter))
+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.
-*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 `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.
+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.
@@ -269,19 +226,12 @@ 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.
-// 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
```
diff --git a/src/pages/type-classes/instance-selection.md b/src/pages/type-classes/instance-selection.md
index 00f953c3..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,17 +59,17 @@ 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)
```
```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
```
@@ -98,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
@@ -133,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**
@@ -160,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.
@@ -169,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:
@@ -179,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:
diff --git a/src/pages/type-classes/printable.md b/src/pages/type-classes/printable.md
index 8153fb17..1e86b214 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,36 +28,29 @@ 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`:
+Then we define some default *instances* of `Printable`:
```scala mdoc:silent
-object PrintableInstances {
- implicit val stringPrintable = new Printable[String] {
- def format(input: String) = input
- }
+given stringPrintable: Printable[String] with
+ def format(input: String) = input
- implicit val intPrintable = new Printable[Int] {
- 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 {
- def format[A](input: A)(implicit p: Printable[A]): String =
+object Printable:
+ 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 +96,13 @@ These either go into the companion object of `Cat`
or a separate object to act as a namespace:
```scala mdoc:silent
-import PrintableInstances._
-
-implicit val catPrintable = 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
@@ -135,45 +124,35 @@ 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`.
-
- 2. Inside `PrintableSyntax` define an `implicit class PrintableOps[A]`
- to wrap up a value of type `A`.
+ 1. Define an `extension [A](value: A)` to wrap up a value of type `A`.
- 3. In `PrintableOps` define the following methods:
+ 2. Define the following extension methods:
- - `format` accepts an implicit `Printable[A]`
+ - `format` using a `Printable[A]`
and returns a `String` representation of the wrapped `A`;
- - `print` accepts an implicit `Printable[A]` and returns `Unit`.
+ - `print` using a `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
-object PrintableSyntax {
- implicit class PrintableOps[A](value: A) {
- def format(implicit p: Printable[A]): String =
- p.format(value)
+extension [A](value: A)
+ 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))
```
-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`:
-```scala mdoc:silent
-import PrintableSyntax._
-```
-
```scala mdoc
Cat("Garfield", 41, "ginger and black").print
```
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:
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"