diff --git a/dist/scala-with-cats.epub b/dist/scala-with-cats.epub index 8442e1c8..e79dd2cb 100644 Binary files a/dist/scala-with-cats.epub and b/dist/scala-with-cats.epub differ diff --git a/dist/scala-with-cats.html b/dist/scala-with-cats.html index 276214ea..801fca9b 100644 --- a/dist/scala-with-cats.html +++ b/dist/scala-with-cats.html @@ -9978,19 +9978,19 @@

randoms.take(5)
 // res19: List[Double] = List(
-//   0.00579240678856785,
-//   0.27467053523994023,
-//   0.6168815655978637,
-//   0.07048256513105311,
-//   0.573129507199479
+//   0.9815709756062859,
+//   0.28209705240248073,
+//   0.17692738597312574,
+//   0.6583410813610281,
+//   0.22551887979099683
 // )
 randoms.take(5)
 // res20: List[Double] = List(
-//   0.3805850631786687,
-//   0.5064645679068113,
-//   0.7876918555972987,
-//   0.8309416134228411,
-//   0.8366745103119307
+//   0.10647242182431838,
+//   0.9422816636323345,
+//   0.19684940126047634,
+//   0.7265388866125337,
+//   0.042925547854420154
 // )

Now let’s define the same stream in a call by need style, using lazy val.

@@ -10003,19 +10003,19 @@

randomsByNeed.take(5)
 // res21: List[Double] = List(
-//   0.03346048190683415,
-//   0.03346048190683415,
-//   0.03346048190683415,
-//   0.03346048190683415,
-//   0.03346048190683415
+//   0.997105748403435,
+//   0.997105748403435,
+//   0.997105748403435,
+//   0.997105748403435,
+//   0.997105748403435
 // )
 randomsByNeed.take(5)
 // res22: List[Double] = List(
-//   0.03346048190683415,
-//   0.03346048190683415,
-//   0.03346048190683415,
-//   0.03346048190683415,
-//   0.03346048190683415
+//   0.997105748403435,
+//   0.997105748403435,
+//   0.997105748403435,
+//   0.997105748403435,
+//   0.997105748403435
 // )

If we wanted a stream that had a different random number for each element but those numbers were constant, we could redefine @@ -10035,19 +10035,19 @@

randomsByNeed2.take(5)
 // res23: List[Double] = List(
-//   0.9771900215781261,
-//   0.4383361680325434,
-//   0.6645095984529569,
-//   0.6719036485094727,
-//   0.8967127697079207
+//   0.32390565923174075,
+//   0.5842965840218141,
+//   0.4183464674983819,
+//   0.5083154197063308,
+//   0.7200935611895052
 // )
 randomsByNeed2.take(5)
 // res24: List[Double] = List(
-//   0.9771900215781261,
-//   0.4383361680325434,
-//   0.6645095984529569,
-//   0.6719036485094727,
-//   0.8967127697079207
+//   0.32390565923174075,
+//   0.5842965840218141,
+//   0.4183464674983819,
+//   0.5083154197063308,
+//   0.7200935611895052
 // )

These subtleties are one of the reasons that functional programmers try to avoid using state as far as possible.

@@ -11041,8 +11041,9 @@

import JsonWriterInstances.given
 
 summon[JsonWriter[String]]
-// res5: JsonWriter[String] = repl.MdocSession$MdocApp3$JsonWriterInstances$$anon$6@645f2f9d
+// res5: JsonWriter[String] = repl.MdocSession$MdocApp3$JsonWriterInstances$$anon$6@6b9dce2d

Most type classes in Cats provide other means to summon instances. However, summon is a good fallback for debugging purposes. We can insert a call to summon @@ -11486,7 +11487,7 @@

val showInt = Show.apply[Int]
-// showInt: Show[Int] = cats.Show$$$Lambda$18067/0x00007fdbbec82678@70483ba3
+// showInt: Show[Int] = cats.Show$$$Lambda$18084/0x00007fe706c91ed0@513af4a9

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.

@@ -11570,7 +11571,7 @@

s"${date.getTime}ms since the epoch." }
new Date().show
-// res1: String = "1716196208976ms since the epoch."
+// res1: String = "1716196993344ms since the epoch."

However, Cats also provides a couple of convenient methods to simplify the process. There are two construction methods on the companion object of Show that we can use to define @@ -14489,10 +14490,10 @@

val func = (x: Int) => x + 1
-// func: Function1[Int, Int] = repl.MdocSession$MdocApp0$$$Lambda$19546/0x00007fdbbee89a30@ea17f39
+// func: Function1[Int, Int] = repl.MdocSession$MdocApp0$$$Lambda$19544/0x00007fe706e8fa30@2a6cd408
 
 val liftedFunc = Functor[Option].lift(func)
-// liftedFunc: Function1[Option[Int], Option[Int]] = cats.Functor$$Lambda$19547/0x00007fdbbee74688@61806c98
+// liftedFunc: Function1[Option[Int], Option[Int]] = cats.Functor$$Lambda$19545/0x00007fe706e95420@1b3cb92b
 
 liftedFunc(Option(1))
 // res1: Option[Int] = Some(value = 2)
@@ -15207,7 +15208,7 @@

val func1 = (x: Int) => x.toDouble val func2 = (y: Double) => y * 2
val func3 = func1.map(func2)
-// func3: Function1[Int, Double] = scala.Function1$$Lambda$19496/0x00007fdbbee22118@26db7aad
+// func3: Function1[Int, Double] = scala.Function1$$Lambda$19456/0x00007fe706e12118@48278cf4

Function1 has two type parameters (the function argument and the result type):

trait Function1[-A, +B] {
@@ -15373,7 +15374,7 @@ 

val func2b: Double <= Double = func2

val func3c = func2b.contramap(func1)
-// func3c: Function1[Int, Double] = scala.Function1$$Lambda$19496/0x00007fdbbee22118@8a7924f
+// func3c: Function1[Int, Double] = scala.Function1$$Lambda$19456/0x00007fe706e12118@b4f0e2

The difference between func2 and func2b is purely syntactic—both refer to the same value and the type aliases are otherwise completely compatible. Incredibly, however, @@ -15913,7 +15914,7 @@

import scala.concurrent.ExecutionContext.Implicits.global
val fm = Monad[Future]
-// fm: Monad[[T >: Nothing <: Any] => Future[T]] = cats.instances.FutureInstances$$anon$1@1fc062b2
+// fm: Monad[[T >: Nothing <: Any] => Future[T]] = cats.instances.FutureInstances$$anon$1@491d54df

The Monad instance uses the captured ExecutionContext for subsequent calls to pure and flatMap:

@@ -16562,12 +16563,12 @@

math.random() } // Computing X -// x: Double = 0.6321481280500852 +// x: Double = 0.517294465012969 x // first access -// res0: Double = 0.6321481280500852 // first access +// res0: Double = 0.517294465012969 // first access x // second access -// res1: Double = 0.6321481280500852 +// res1: Double = 0.517294465012969

This is an example of call-by-value evaluation:

  • the computation is evaluated at point where it is defined @@ -16584,10 +16585,10 @@

    y // first access // Computing Y -// res2: Double = 0.012167893501190075 // first access +// res2: Double = 0.3438937352732575 // first access y // second access // Computing Y -// res3: Double = 0.7714666480953452 +// res3: Double = 0.6994851605732413

    These are the properties of call-by-name evaluation:

    • the computation is evaluated at the point of use (lazy); @@ -16606,9 +16607,9 @@

      z // first access // Computing Z -// res4: Double = 0.9192647328162591 // first access +// res4: Double = 0.8783277868352664 // first access z // second access -// res5: Double = 0.9192647328162591 +// res5: Double = 0.8783277868352664

      Let’s summarize. There are two properties of interest:

      • evaluation at the point of definition (eager) versus at the @@ -16634,19 +16635,19 @@

        import cats.Eval
        val now = Eval.now(math.random() + 1000)
        -// now: Eval[Double] = Now(value = 1000.3648115892809)
        +// now: Eval[Double] = Now(value = 1000.9650954739952)
         val always = Eval.always(math.random() + 3000)
        -// always: Eval[Double] = cats.Always@64643697
        +// always: Eval[Double] = cats.Always@376da6ab
         val later = Eval.later(math.random() + 2000)
        -// later: Eval[Double] = cats.Later@6bcd9902
        +// later: Eval[Double] = cats.Later@602a42d5

        We can extract the result of an Eval using its value method:

        now.value
        -// res6: Double = 1000.3648115892809
        +// res6: Double = 1000.9650954739952
         always.value
        -// res7: Double = 3000.466869567198
        +// res7: Double = 3000.2731813960345
         later.value
        -// res8: Double = 2000.0949690465334
        +// res8: Double = 2000.4370179150637

        Each type of Eval calculates its result using one of the evaluation models defined above. Eval.now captures a value right now. Its semantics are similar to a @@ -16656,39 +16657,39 @@

        math.random() } // Computing X -// x: Eval[Double] = Now(value = 0.24895483529215334) +// x: Eval[Double] = Now(value = 0.9094504639479744) x.value // first access -// res10: Double = 0.24895483529215334 // first access +// res10: Double = 0.9094504639479744 // first access x.value // second access -// res11: Double = 0.24895483529215334 +// res11: Double = 0.9094504639479744

        Eval.always captures a lazy computation, similar to a def:

        val y = Eval.always{
           println("Computing Y")
           math.random()
         }
        -// y: Eval[Double] = cats.Always@388d61b1
        +// y: Eval[Double] = cats.Always@68d500be
         
         y.value // first access
         // Computing Y
        -// res12: Double = 0.4101650712443994 // first access
        +// res12: Double = 0.4109022424918457 // first access
         y.value // second access
         // Computing Y
        -// res13: Double = 0.4981025235006662
        +// res13: Double = 0.25297219585444886

        Finally, Eval.later captures a lazy, memoized computation, similar to a lazy val:

        val z = Eval.later{
           println("Computing Z")
           math.random()
         }
        -// z: Eval[Double] = cats.Later@f37a328
        +// z: Eval[Double] = cats.Later@50b673d6
         
         z.value // first access
         // Computing Z
        -// res14: Double = 0.6549553350257552 // first access
        +// res14: Double = 0.8455416798865288 // first access
         z.value // second access
        -// res15: Double = 0.6549553350257552
        +// res15: Double = 0.8455416798865288

        The three behaviours are summarized below:

        @@ -16733,7 +16734,7 @@

        val greeting = Eval
           .always{ println("Step 1"); "Hello" }
           .map{ str => println("Step 2"); s"$str world" }
        -// greeting: Eval[String] = cats.Eval$$anon$4@4aa2ecd7
        +// greeting: Eval[String] = cats.Eval$$anon$4@a34cf95
         
         greeting.value
         // Step 1
        @@ -16750,7 +16751,7 @@ 

        a + b } // Calculating A -// ans: Eval[Int] = cats.Eval$$anon$4@4da07908 +// ans: Eval[Int] = cats.Eval$$anon$4@383a18ec ans.value // first access // Calculating B @@ -16769,7 +16770,7 @@

        .map{ str => println("Step 2"); s"$str sat on" } .memoize .map{ str => println("Step 3"); s"$str the mat" } -// saying: Eval[String] = cats.Eval$$anon$4@1ef309e4 +// saying: Eval[String] = cats.Eval$$anon$4@782a3fa5 saying.value // first access // Step 1 @@ -17186,7 +17187,7 @@

        val catName: Reader[Cat, String] = Reader(cat => cat.name) // catName: Kleisli[Id, Cat, String] = Kleisli( -// run = repl.MdocSession$MdocApp0$$$Lambda$20385/0x00007fdbbef08000@63d0f50b +// run = repl.MdocSession$MdocApp0$$$Lambda$20420/0x00007fe706eaa000@46facb0 // )

        We can extract the function again using the Reader's run method and call it using apply as @@ -17434,27 +17435,27 @@

        val getDemo = State.get[Int]
        -// getDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Int] = cats.data.IndexedStateT@482b0b7e
        +// getDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Int] = cats.data.IndexedStateT@48f4b7db
         getDemo.run(10).value
         // res1: Tuple2[Int, Int] = (10, 10)
         
         val setDemo = State.set[Int](30)
        -// setDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Unit] = cats.data.IndexedStateT@54d00be2
        +// setDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Unit] = cats.data.IndexedStateT@1e339d1e
         setDemo.run(10).value
         // res2: Tuple2[Int, Unit] = (30, ())
         
         val pureDemo = State.pure[Int, String]("Result")
        -// pureDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, String] = cats.data.IndexedStateT@9785b8c
        +// pureDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, String] = cats.data.IndexedStateT@74487c21
         pureDemo.run(10).value
         // res3: Tuple2[Int, String] = (10, "Result")
         
         val inspectDemo = State.inspect[Int, String](x => s"${x}!")
        -// inspectDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, String] = cats.data.IndexedStateT@7e83dc7d
        +// inspectDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, String] = cats.data.IndexedStateT@6ef54a1d
         inspectDemo.run(10).value
         // res4: Tuple2[Int, String] = (10, "10!")
         
         val modifyDemo = State.modify[Int](_ + 1)
        -// modifyDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Unit] = cats.data.IndexedStateT@6e634828
        +// modifyDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Unit] = cats.data.IndexedStateT@3526277
         modifyDemo.run(10).value
         // res5: Tuple2[Int, Unit] = (11, ())

        We can assemble these building blocks using a for comprehension. @@ -17469,7 +17470,7 @@

        _ <- modify[Int](_ + 1) c <- inspect[Int, Int](_ * 1000) } yield (a, b, c) -// program: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Tuple3[Int, Int, Int]] = cats.data.IndexedStateT@3ab8d11d +// program: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Tuple3[Int, Int, Int]] = cats.data.IndexedStateT@10bc46e val (state, result) = program.run(1).value // state: Int = 3 @@ -17585,7 +17586,7 @@

        _ <- evalOne("2") ans <- evalOne("+") } yield ans -// program: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@715749fd +// program: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@3615191a program.runA(Nil).value // res11: Int = 3 @@ -17612,7 +17613,7 @@

        val multistageProgram = evalAll(List("1", "2", "+", "3", "*"))
        -// multistageProgram: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@11d7139e
        +// multistageProgram: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@72740867
         
         multistageProgram.runA(Nil).value
         // res13: Int = 9
        @@ -17627,7 +17628,7 @@

        _ <- evalAll(List("3", "4", "+")) ans <- evalOne("*") } yield ans -// biggerProgram: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@65812b84 +// biggerProgram: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@62259d84 biggerProgram.runA(Nil).value // res14: Int = 21 @@ -18598,7 +18599,7 @@

        10. function that accepts the wrong number or types of parameters, we get a compile error:

        val add: (Int, Int) => Int = (a, b) => a + b
        -// add: Function2[Int, Int, Int] = repl.MdocSession$MdocApp0$$$Lambda$18094/0x00007fdbbec9af68@79308f64
        +// add: Function2[Int, Int, Int] = repl.MdocSession$MdocApp0$$$Lambda$18124/0x00007fe706ca7538@7ef18230
        (Option(1), Option(2), Option(3)).mapN(add)
         // error:
         // ':' expected, but '(' found
        diff --git a/dist/scala-with-cats.pdf b/dist/scala-with-cats.pdf
        index 4d32eb48..1e2f61e7 100644
        Binary files a/dist/scala-with-cats.pdf and b/dist/scala-with-cats.pdf differ