From a10d4b6c73e51109aa1958074848c5800d811a65 Mon Sep 17 00:00:00 2001 From: James Ward Date: Tue, 23 Jul 2024 21:41:10 -0600 Subject: [PATCH] improvements to composeability #bruce #bill #james #time 1h --- Chapters/05_Testing.md | 19 ++++++--- Chapters/06_Failure.md | 18 ++++---- Chapters/07_Composability.md | 80 ++++++++++++++++++++++++------------ 3 files changed, 78 insertions(+), 39 deletions(-) diff --git a/Chapters/05_Testing.md b/Chapters/05_Testing.md index 8106c3dd..149ea7a7 100644 --- a/Chapters/05_Testing.md +++ b/Chapters/05_Testing.md @@ -194,8 +194,11 @@ import zio.* class Material(val brittleness: Int) object Material: - val wood = ZLayer.succeed(Material(brittleness = 5)) - val plastic = ZLayer.succeed(Material(brittleness = 10)) + val wood = + ZLayer.succeed(Material(brittleness = 5)) + val plastic = + ZLayer + .succeed(Material(brittleness = 10)) ``` Different types of `Saw` and `Nailer` each have an `intensity` that relates it to `Material.brittleness`. @@ -207,14 +210,18 @@ import zio.Console.* class Saw(val intensity: Int) object Saw: - val hand = ZLayer.succeed(Saw(intensity = 4)) - val robotic = ZLayer.succeed(Saw(intensity = 8)) + val hand = + ZLayer.succeed(Saw(intensity = 4)) + val robotic = + ZLayer.succeed(Saw(intensity = 8)) class Nailer(val intensity: Int) object Nailer: - val hand = ZLayer.succeed(Nailer(intensity = 4)) - val robotic = ZLayer.succeed(Nailer(intensity = 11)) + val hand = + ZLayer.succeed(Nailer(intensity = 4)) + val robotic = + ZLayer.succeed(Nailer(intensity = 11)) ```` The test takes a `Material` and checks it against a `Saw` and a `Nailer`: diff --git a/Chapters/06_Failure.md b/Chapters/06_Failure.md index 0fa8a10b..46877ae6 100644 --- a/Chapters/06_Failure.md +++ b/Chapters/06_Failure.md @@ -69,7 +69,8 @@ import zio.* case object FailObject object FailException extends Exception: - override def toString: String = "FailException" + override def toString: String = + "FailException" def failureTypes(n: Int) = n match @@ -146,7 +147,8 @@ import zio.Console.* def shortCircuit(lim: Int) = defer: - printLine(s"-- shortCircuit($lim) --").run + printLine(s"-- shortCircuit($lim) --") + .run val r1 = testLimit(0, lim).run printLine(s"-> n: $lim, r1: $r1").run val r2 = testLimit(1, lim).run @@ -406,10 +408,10 @@ If you try to catch failures from `temperatureAppComplete` by running the follow ```scala 3 mdoc:fail def compilerError = - temperatureAppComplete.catchAll: - case ex: GpsException => - ZIO.succeed: - "This cannot happen" + temperatureAppComplete.catchAll: + case ex: GpsException => + ZIO.succeed: + "This cannot happen" ``` The compiler reports that we included unusable code. @@ -427,7 +429,9 @@ import zio.Console.* def check(t: Temperature) = defer: - printLine("Checking Temperature").orDie.run + printLine("Checking Temperature") + .orDie + .run if t.degrees > 0 then ZIO .succeed: diff --git a/Chapters/07_Composability.md b/Chapters/07_Composability.md index f8003b2e..65e142b8 100644 --- a/Chapters/07_Composability.md +++ b/Chapters/07_Composability.md @@ -26,6 +26,8 @@ These concepts and their competing solutions will be expanded on and contrasted In this chapter, we use several pre-defined functions. The implementations are deliberately hidden to highlight the surprising nature of executing Effects and to maintain focus on composability. +(We start by showing composability with all these other non-ZIO Scala data types.) + ## Universal Composability ```scala 3 mdoc:invisible @@ -328,33 +330,36 @@ trait File extends AutoCloseable: def contains(searchTerm: String): Boolean def write(entry: String): Try[String] def summaryFor(searchTerm: String): String - def sameContent(other: File): Boolean def content(): String +def sameContents( + files: List[File] +): Boolean = + println: + "side-effect print: comparing content" + + files + .tail + .forall( + _.content() == files.head.content() + ) + def openFile(path: String) = new File: var contents: List[String] = List("Medical Breakthrough!") - println("File - OPEN") + println(s"File - OPEN: $path") override def content() = path match case "file1.txt" | "file2.txt" | - "summaries.txt" => + "file3.txt" | "summaries.txt" => "hot dog" case _ => "not hot dog" - override def sameContent( - other: File - ): Boolean = - println( - "side-effect print: comparing content" - ) - content() == other.content() - override def close = - println("File - CLOSE") + println(s"File - CLOSE: $path") override def contains( searchTerm: String @@ -420,6 +425,7 @@ Instead, we use `ZIO.fromAutoCloseable`: import zio.* import zio.direct.* +// TODO explain that fromAutoClosable takes a ZIO, thus the ZIO.succeed def openFileZ(path: String) = ZIO.fromAutoCloseable: ZIO.succeed: @@ -449,20 +455,20 @@ import zio.* import zio.direct.* import scala.util.Using -import java.io.FileReader - -val staticScoped = - Using(openFile("file1.txt")): - file1 => - Using(openFile("file2.txt")): - file2 => - println: - file1.sameContent(file2) def run = defer: - staticScoped - .ignore + Using(openFile("file1.txt")): + file1 => + Using(openFile("file2.txt")): + file2 => + Using(openFile("file3.txt")): + file3 => + sameContents: + List(file1, file2, file3) + .get + .get + .get ``` You can see that each new file means an additional level of code nesting. @@ -477,13 +483,35 @@ def run = defer: val file1 = openFileZ("file1.txt").run val file2 = openFileZ("file2.txt").run - printLine: - file1.sameContent(file2) - .run + val file3 = openFileZ("file3.txt").run + sameContents: + List(file1, file2, file3) ``` The Effect Oriented code remains flat. +Try with resources only works with structured code, not dynamic values. + +```scala 3 mdoc:runzio +import zio.* +import zio.direct.* +import zio.Console.* + +def run = + defer: + val fileNames = + List( + "file1.txt", + "file2.txt", + "file3.txt", + ) + + val files = + ZIO.foreach(fileNames)(openFileZ).run + + sameContents(files) +``` + ## Try Now let's write to a `File`.