diff --git a/dist/scala-with-cats.epub b/dist/scala-with-cats.epub index 6f875a91..7772a650 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 a7239a86..7c927f74 100644 --- a/dist/scala-with-cats.html +++ b/dist/scala-with-cats.html @@ -9911,19 +9911,19 @@

randoms.take(5)
 // res19: List[Double] = List(
-//   0.9208355396390738,
-//   0.5891123064177592,
-//   0.3863326062180119,
-//   0.687954952883182,
-//   0.6468041118151546
+//   0.8754399846226281,
+//   0.8668667032261326,
+//   0.8096271659320063,
+//   0.04478363704070387,
+//   0.5750767027125137
 // )
 randoms.take(5)
 // res20: List[Double] = List(
-//   0.04387754397318444,
-//   0.6949346853301758,
-//   0.01805741082006429,
-//   0.2880136805156752,
-//   0.3376311452930971
+//   0.21185116186743658,
+//   0.3035319210159354,
+//   0.40533845806072466,
+//   0.7381884666336406,
+//   0.34627861681944083
 // )

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

@@ -9936,19 +9936,19 @@

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

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

randomsByNeed2.take(5)
 // res23: List[Double] = List(
-//   0.8940500869022447,
-//   0.3731405707113826,
-//   0.6282375992855587,
-//   0.6939341360566086,
-//   0.3712126709086925
+//   0.12069142328271365,
+//   0.3166763080822603,
+//   0.05042767731721132,
+//   0.8940362612630662,
+//   0.8562348066084499
 // )
 randomsByNeed2.take(5)
 // res24: List[Double] = List(
-//   0.8940500869022447,
-//   0.3731405707113826,
-//   0.6282375992855587,
-//   0.6939341360566086,
-//   0.3712126709086925
+//   0.12069142328271365,
+//   0.3166763080822603,
+//   0.05042767731721132,
+//   0.8940362612630662,
+//   0.8562348066084499
 // )

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

@@ -11097,7 +11097,7 @@

summon[JsonWriter[String]]
-// res6: JsonWriter[String] = repl.MdocSession$MdocApp3$JsonWriter$$anon$7@12a11c50
+// res6: JsonWriter[String] = repl.MdocSession$MdocApp3$JsonWriter$$anon$7@11022749

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 @@ -13122,7 +13122,7 @@

def show(date: Date): String = s"${date.getTime}ms since the epoch."
new Date().show
-// res2: String = "1723213389727ms since the epoch."
+// res2: String = "1723214210614ms 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 @@ -14450,10 +14450,10 @@

val func = (x: Int) => x + 1
-// func: Function1[Int, Int] = repl.MdocSession$MdocApp0$$$Lambda/0x00007fd16b11ca10@50c85e5f
+// func: Function1[Int, Int] = repl.MdocSession$MdocApp0$$$Lambda/0x00007f438ae98a10@2c84be94
 
 val liftedFunc = Functor[Option].lift(func)
-// liftedFunc: Function1[Option[Int], Option[Int]] = cats.Functor$$Lambda/0x00007fd16b1091c8@61233264
+// liftedFunc: Function1[Option[Int], Option[Int]] = cats.Functor$$Lambda/0x00007f438aecf568@41aee62c
 
 liftedFunc(Option(1))
 // res1: Option[Int] = Some(value = 2)
@@ -15156,7 +15156,7 @@

val func1 = (x: Int) => x.toDouble val func2 = (y: Double) => y * 2
val func3 = func1.map(func2)
-// func3: Function1[Int, Double] = cats.instances.Function1Instances0$$anon$11$$Lambda/0x00007fd16b0d62f0@42a92565
+// func3: Function1[Int, Double] = cats.instances.Function1Instances0$$anon$11$$Lambda/0x00007f438ae8cad0@464123f7

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

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

val func2b: Double <= Double = func2

val func3c = func2b.contramap(func1)
-// func3c: Function1[Int, Double] = scala.Function1$$Lambda/0x00007fd16b0843c8@2774ca70
+// func3c: Function1[Int, Double] = scala.Function1$$Lambda/0x00007f438ae927b0@65b0575f

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, @@ -15849,7 +15849,7 @@

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

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

@@ -16490,12 +16490,12 @@

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

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

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

    y // first access // Computing Y -// res2: Double = 0.8766830953931537 +// res2: Double = 0.6865324699135692 y // second access // Computing Y -// res3: Double = 0.9385360256815104 +// res3: Double = 0.8828116724033305

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

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

      z // first access // Computing Z -// res4: Double = 0.9591366196571159 +// res4: Double = 0.5805087513028249 z // second access -// res5: Double = 0.9591366196571159 +// res5: Double = 0.5805087513028249

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

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

        import cats.Eval
        val now = Eval.now(math.random() + 1000)
        -// now: Eval[Double] = Now(value = 1000.2789904655458)
        +// now: Eval[Double] = Now(value = 1000.0855198365294)
         val always = Eval.always(math.random() + 3000)
        -// always: Eval[Double] = cats.Always@76cf20f0
        +// always: Eval[Double] = cats.Always@52ba4f69
         val later = Eval.later(math.random() + 2000)
        -// later: Eval[Double] = cats.Later@402829e2
        +// later: Eval[Double] = cats.Later@3acc34c5

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

        now.value
        -// res6: Double = 1000.2789904655458
        +// res6: Double = 1000.0855198365294
         always.value
        -// res7: Double = 3000.397989875432
        +// res7: Double = 3000.2588105234813
         later.value
        -// res8: Double = 2000.0977959520712
        +// res8: Double = 2000.7356027225082

        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 @@ -16584,39 +16584,39 @@

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

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

        val y = Eval.always{
           println("Computing Y")
           math.random()
         }
        -// y: Eval[Double] = cats.Always@51fc1d
        +// y: Eval[Double] = cats.Always@17e51b5e
         
         y.value // first access
         // Computing Y
        -// res12: Double = 0.7103047555919701
        +// res12: Double = 0.5136525236943111
         y.value // second access
         // Computing Y
        -// res13: Double = 0.5434137244149811
        +// res13: Double = 0.3544535206672599

        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@7855c76e
        +// z: Eval[Double] = cats.Later@e7d6f7
         
         z.value // first access
         // Computing Z
        -// res14: Double = 0.7290651872811074
        +// res14: Double = 0.5130255926081816
         z.value // second access
        -// res15: Double = 0.7290651872811074
        +// res15: Double = 0.5130255926081816

        The three behaviours are summarized below:

        @@ -16661,7 +16661,7 @@

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

        a + b } // Calculating A -// ans: Eval[Int] = cats.Eval$$anon$4@6e97ba5b +// ans: Eval[Int] = cats.Eval$$anon$4@28efa9f6 ans.value // first access // Calculating B @@ -16697,7 +16697,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@7c836ecb +// saying: Eval[String] = cats.Eval$$anon$4@70352219 saying.value // first access // Step 1 @@ -17114,7 +17114,7 @@

        val catName: Reader[Cat, String] = Reader(cat => cat.name) // catName: Kleisli[Id, Cat, String] = Kleisli( -// run = repl.MdocSession$MdocApp0$$$Lambda/0x00007fd16b201000@72987df3 +// run = repl.MdocSession$MdocApp0$$$Lambda/0x00007f438af4b000@3c1ed5a1 // )

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

        val getDemo = State.get[Int]
        -// getDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Int] = cats.data.IndexedStateT@366cf74
        +// getDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Int] = cats.data.IndexedStateT@645716b0
         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@5595c481
        +// setDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Unit] = cats.data.IndexedStateT@12aa3e79
         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@2c8d93d3
        +// pureDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, String] = cats.data.IndexedStateT@720ef7b4
         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@76dbb40c
        +// inspectDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, String] = cats.data.IndexedStateT@2e720267
         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@3de71dca
        +// modifyDemo: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Unit] = cats.data.IndexedStateT@49e1d32
         modifyDemo.run(10).value
         // res5: Tuple2[Int, Unit] = (11, ())

        We can assemble these building blocks using a for comprehension. @@ -17397,7 +17397,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@2b35a910 +// program: IndexedStateT[[A >: Nothing <: Any] => Eval[A], Int, Int, Tuple3[Int, Int, Int]] = cats.data.IndexedStateT@5546e899 val (state, result) = program.run(1).value // state: Int = 3 @@ -17513,7 +17513,7 @@

        _ <- evalOne("2") ans <- evalOne("+") } yield ans -// program: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@57edf64e +// program: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@2c1734f9 program.runA(Nil).value // res11: Int = 3 @@ -17540,7 +17540,7 @@

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

        _ <- evalAll(List("3", "4", "+")) ans <- evalOne("*") } yield ans -// biggerProgram: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@5f6a7bdb +// biggerProgram: IndexedStateT[[A >: Nothing <: Any] => Eval[A], List[Int], List[Int], Int] = cats.data.IndexedStateT@268ea81e biggerProgram.runA(Nil).value // res14: Int = 21 @@ -18526,7 +18526,7 @@

        11. 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/0x00007fd16af04f30@4c12ccc5
        +// add: Function2[Int, Int, Int] = repl.MdocSession$MdocApp0$$$Lambda/0x00007f438ac7cf30@75d16ee6
        (Option(1), Option(2), Option(3)).mapN(add)
         // error: 
         // ':' expected, but '(' found
        @@ -20096,7 +20096,7 @@ 

        head: Vector[String], body: Vector[String] ) { - // Head tags ------------------------------------------------------------- + // Head tags --------------------------------------------- def head(using S =:= Empty): Html[InHead, WithoutTitle] = Html(head, body) @@ -20109,7 +20109,7 @@

        def link(rel: String, href: String)(using S =:= InHead): Html[InHead, T] = Html(head :+ s"<link rel=\"$rel\" href=\"$href\"/>", body) - // Body tags ------------------------------------------------------------- + // Body tags --------------------------------------------- def body(using S =:= InHead, T =:= WithTitle): Html[InBody, WithTitle] = Html(head, body) @@ -20120,7 +20120,7 @@

        def p(text: String)(using S =:= InBody): Html[InBody, T] = Html(head, body :+ s"<p>$text</p>") - // Interpreter ----------------------------------------------------------- + // Interpreter ------------------------------------------ override def toString(): String = { val h = head.mkString(" <head>\n ", "\n ", "\n </head>") diff --git a/dist/scala-with-cats.pdf b/dist/scala-with-cats.pdf index 99aa06e2..622ba7b2 100644 Binary files a/dist/scala-with-cats.pdf and b/dist/scala-with-cats.pdf differ