package net.degoes.zio import java.io.IOException import zio._ import java.text.NumberFormat import java.nio.charset.StandardCharsets import zio.blocking.Blocking import zio.console.Console object ZIOTypes { type ??? = Nothing // ZIO[R, E, A] /** * EXERCISE * * Provide definitions for the ZIO type aliases below. */ type Task[+A] = ZIO[Any, Throwable, A] type UIO[+A] = ZIO[Any, Nothing, A] type RIO[-R, +A] = ZIO[R, Throwable, A] type IO[+E, +A] = ZIO[Any, E, A] type URIO[-R, +A] = ZIO[R, Nothing, A] } object HelloWorld extends App { import zio.console._ /** * EXERCISE * * Implement a simple "Hello World!" program using the effect returned by `putStrLn`. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = putStrLn("Hello world!").as(0) } object PrintSequence extends App { import zio.console._ /** * EXERCISE * * Using `*>` (`zipRight`), compose a sequence of `putStrLn` effects to * produce an effect that prints three lines of text to the console. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = putStrLn("Hello") *> putStrLn("ZIO") *> putStrLn("World!") as 0 } object ErrorRecovery extends App { val StdInputFailed = 1 import zio.console._ val failed = putStrLn("About to fail...") *> ZIO.fail("Uh oh!") *> putStrLn("This will NEVER be printed!") /** * EXERCISE * * Using `ZIO#orElse` or `ZIO#fold`, have the `run` function compose the * preceding `failed` effect into the effect that `run` returns. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = failed.orDieWith(s => new Error(s)) as 0 //failed.catchAll(error => putStrLn(error).as(1)) as 0 //(failed as 0) orElse ZIO.succeed(1) //failed.fold(success = _ => 1, failure = _ => 0) } object Looping extends App { import zio.console._ /** * EXERCISE * * Implement a `repeat` combinator using `flatMap` and recursion. */ def repeat[R, E, A](n: Int)(effect: ZIO[R, E, A]): ZIO[R, E, A] = if (n <= 1) effect else effect *> repeat(n - 1)(effect) // else effect.flatMap(_ => repeat(n - 1)(effect)) def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = repeat(3)(putStrLn("All work and no play makes Jack a dull boy")) as 0 } object EffectConversion extends App { /** * EXERCISE * * Using ZIO.effect, convert the side-effecting of `println` into a pure * functional effect. */ def myPrintLn(line: String): Task[Unit] = // converts exceptions into ZIO.fail ZIO.effect(println(line)) def run(args: List[String]) = myPrintLn("foo").fold(_ => 1, _ => 0) } object ErrorNarrowing extends App { import java.io.IOException import scala.io.StdIn.readLine implicit class Unimplemented[A](v: A) { def ? = ??? } /** * EXERCISE * * Using `ZIO#refineToOrDie`, narrow the error type of the following * effect to IOException. */ val myReadLine: IO[IOException, String] = // convert Exception to IOException and put it on the typed channel, // if any other errors then die (unrecoverable error) ZIO.effect(readLine()).refineToOrDie[IOException] def myPrintLn(line: String): UIO[Unit] = UIO(println(line)) def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = (for { _ <- myPrintLn("What is your name?") name <- myReadLine _ <- myPrintLn(s"Good to meet you, ${name}") } yield 0) orElse ZIO.succeed(1) } object PromptName extends App { val StdInputFailed = 1 import zio.console._ /** * EXERCISE * * Using `ZIO#flatMap`, implement a simple program that asks the user for * their name (using `getStrLn`), and then prints it out to the user (using `putStrLn`). */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = putStrLn("What is your name?") *> getStrLn .flatMap(name => putStrLn(s"Hello $name")) .fold(_ => 1, _ => 0) } object NumberGuesser extends App { import zio.console._ import zio.random._ def analyzeAnswer(random: Int, guess: String) = if (random.toString == guess.trim) putStrLn("You guessed correctly!") else putStrLn(s"You did not guess correctly. The answer was ${random}") /** * EXERCISE * * Choose a random number (using `nextInt`), and then ask the user to guess * the number, feeding their response to `analyzeAnswer`, above. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = putStrLn("Pick a random number") *> (nextInt(3) <*> getStrLn) .flatMap { case (random, guess) => analyzeAnswer(random + 1, guess) } .fold(_ => 1, _ => 0) } object AlarmApp extends App { import zio.console._ import zio.duration._ import java.io.IOException import java.util.concurrent.TimeUnit /** * EXERCISE * * Create an effect that will get a `Duration` from the user, by prompting * the user to enter a decimal number of seconds. Use `refineOrDie` to * narrow the error type as necessary. */ lazy val getAlarmDuration: ZIO[Console, IOException, Duration] = { def parseDuration(input: String): IO[NumberFormatException, Duration] = ZIO.effect(input.toInt) .map(_.seconds) .refineToOrDie[NumberFormatException] def fallback(input: String): ZIO[Console, IOException, Duration] = putStrLn(s"You entered $input which is not valid for seconds, try again!") *> getAlarmDuration for { _ <- putStrLn("Please enter the number of seconds to sleep: ") input <- getStrLn duration <- parseDuration(input) orElse fallback(input) } yield duration } /** * EXERCISE * * Create a program that asks the user for a number of seconds to sleep, * sleeps the specified number of seconds using ZIO.sleep(d), and then * prints out a wakeup alarm message, like "Time to wakeup!!!". */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = (getAlarmDuration.flatMap(ZIO.sleep) *> putStrLn("Time to wakeup!!!")) .fold(_ => 1, _ => 0) } object Cat extends App { import zio.console._ import zio.blocking._ import java.io.IOException /** * EXERCISE * * Implement a function to read a file on the blocking thread pool, storing * the result into a string. */ def readFile(file: String): ZIO[Blocking, IOException, String] = blocking { ZIO.effect() import scala.io.Source val open: Task[Source] = ZIO.effect(Source.fromFile(file)) val close = (s: Source) => ZIO.effect(s.close()).orDie // bracket ensures that cleanup will be done if the resource was opened (even in the face of interruption) open.bracket(close(_)) { source => ZIO.effect(source.mkString) } .refineToOrDie[IOException] } /** * EXERCISE * * Implement a version of the command-line utility "cat", which dumps the * contents of the specified file to standard output. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = if (args.isEmpty) putStrLn("Supply a file as an argument") as 1 else readFile(args.head).flatMap(putStrLn).fold(_ => 2, _ => 0) } import scala.io.Source class ZioSource private (private val source: Source) extends AnyVal { import zio.blocking._ import java.io.IOException def close: ZIO[Blocking, IOException, Unit] = execute(_.close()) def execute[T](f: Source => T): ZIO[Blocking, IOException, T] = effectBlocking(f(source)).refineToOrDie[IOException] } object ZioSource { import zio.blocking._ import java.io.IOException def apply(file: String): ZIO[Blocking, IOException, ZioSource] = effectBlocking(new ZioSource(Source.fromFile(file))).refineToOrDie[IOException] def resource(file: String): ZManaged[Blocking, IOException, ZioSource] = ZManaged.make(ZioSource(file))(_.close.orDie) } object ReadFilesCleanup extends App { import zio.console._ // this is not very composable def readFiles(file1: String, file2: String): ZIO[Console with Blocking, IOException, Unit] = ZioSource(file1).bracket(_.close.orDie) { source1 => ZioSource(file2).bracket(_.close.orDie) { source2 => (source1.execute(_.mkString) <*> source2.execute(_.mkString)) .flatMap { case (l, r) => putStrLn(l) *> putStrLn(r) } } } def readFileBetter(file1: String, file2: String) = (ZioSource.resource(file1) <*> ZioSource.resource(file2)) .use { case (l, r) => (l.execute(_.mkString) <*> r.execute(_.mkString)) .flatMap { case (l, r) => putStrLn(l) *> putStrLn(r) } } override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, Int] = ??? } object SourceManaged extends App { import zio.console._ import zio.blocking._ import zio.duration._ import java.io.IOException import scala.io.Source final class ZioSource private (private val source: Source) { def execute[T](f: Source => T): ZIO[Blocking, IOException, T] = effectBlocking(f(source)).refineToOrDie[IOException] } object ZioSource { /** * EXERCISE * * Use the `ZManaged.make` constructor to make a managed data type that * will automatically acquire and release the resource when it is used. */ def make(file: String): ZManaged[Blocking, IOException, ZioSource] = { // An effect that acquires the resource: val open = effectBlocking(new ZioSource(Source.fromFile(file))).refineToOrDie[IOException] // A function that, when given the resource, returns an effect that // releases the resource: val close: ZioSource => ZIO[Blocking, Nothing, Unit] = _.execute(_.close()).orDie ??? } } /** * EXERCISE * * Implement a function to read a file on the blocking thread pool, storing * the result into a string. */ def readFiles(files: List[String]): ZIO[Blocking with Console, IOException, Unit] = ??? /** * EXERCISE * * Implement a function that prints out all files specified on the * command-line. Only print out contents from these files if they * can all be opened simultaneously. Otherwise, don't print out * anything except an error message. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = ??? } object CatIncremental extends App { import zio.console._ import zio.blocking._ import java.io.{IOException, InputStream, FileInputStream} /** * BONUS EXERCISE * * Implement a `blockingIO` combinator to use in subsequent exercises. */ def blockingIO[A](a: => A): ZIO[Blocking, IOException, A] = ZIO.accessM(_.blocking.effectBlocking(a).refineToOrDie[IOException]) /** * EXERCISE * * Implement all missing methods of `FileHandle`. Be sure to do all work on * the blocking thread pool. */ final case class FileHandle private (private val is: InputStream) { final private def close: ZIO[Blocking, IOException, Unit] = blockingIO(is.close()) final def read: ZIO[Blocking, IOException, Option[Chunk[Byte]]] = blockingIO { val array = Array.ofDim[Byte](1024) val bytesRead = is.read(array) if (bytesRead == -1) None else Some(Chunk.fromArray(array.take(bytesRead))) } } object FileHandle { final def open(file: String): ZManaged[Blocking, IOException, FileHandle] = { val acquire = blockingIO { val inputStream = new FileInputStream(new java.io.File(file)) FileHandle(inputStream) } val release = (f: FileHandle) => f.close.orDie ZManaged.make(acquire)(release) } } def cat(fh: FileHandle): ZIO[Blocking with Console, IOException, Unit] = fh.read.flatMap { case None => ZIO.unit case Some(chunk) => putStr(new String(chunk.toArray, StandardCharsets.UTF_8)) *> cat(fh) } /** * EXERCISE * * Implement an incremental version of the `cat` utility, using `ZIO#bracket` * or `ZManaged` to ensure the file is closed in the event of error or * interruption. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = args match { case file :: Nil => (FileHandle.open(file).use(cat) as 0) orElse ZIO.succeed(1) case _ => putStrLn("Usage: cat <file>") as 2 } } object AlarmAppImproved extends App { import zio.console._ import zio.duration._ import java.io.IOException import java.util.concurrent.TimeUnit lazy val getAlarmDuration: ZIO[Console, IOException, Duration] = { def parseDuration(input: String): IO[NumberFormatException, Duration] = ZIO.effect(Duration((input.toDouble * 1000.0).toLong, TimeUnit.MILLISECONDS)) .refineToOrDie[NumberFormatException] val fallback = putStrLn("You didn't enter the number of seconds!") *> getAlarmDuration for { _ <- putStrLn("Please enter the number of seconds to sleep: ") input <- getStrLn duration <- parseDuration(input) orElse fallback } yield duration } /** * EXERCISE * * Create a program that asks the user for a number of seconds to sleep, * sleeps the specified number of seconds using ZIO.sleep(d), concurrently * prints a dot every second that the alarm is sleeping for, and then * prints out a wakeup alarm message, like "Time to wakeup!!!". */ def run2(args: List[String]): ZIO[ZEnv, Nothing, Int] = (for { sleepFor <- getAlarmDuration sleepFiber <- ZIO.sleep(sleepFor).fork printFiber <- (putStrLn(".") *> ZIO.sleep(1.second)).forever.fork _ <- sleepFiber.join _ <- printFiber.interrupt _ <- putStrLn("Time to wake up!") } yield 0).fold(_ => 1, _ => 0) def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = (for { sleepFor <- getAlarmDuration sleep = ZIO.sleep(sleepFor) print = (putStrLn(".") *> ZIO.sleep(1.second)).forever _ <- sleep race print _ <- putStrLn("Time to wake up!") } yield 0).fold(_ => 1, _ => 0) } object ComputePi extends App { import zio.random._ import zio.console._ import zio.clock._ import zio.duration._ import zio.stm._ /** * Some state to keep track of all points inside a circle, * and total number of points. */ final case class PiState( inside: Ref[Long], total: Ref[Long] ) /** * A function to estimate pi. */ def estimatePi(inside: Long, total: Long): Double = (inside.toDouble / total.toDouble) * 4.0 /** * A helper function that determines if a point lies in * a circle of 1 radius. */ def insideCircle(x: Double, y: Double): Boolean = Math.sqrt(x * x + y * y) <= 1.0 /** * An effect that computes a random (x, y) point. */ val randomPoint: ZIO[Random, Nothing, (Double, Double)] = nextDouble zip nextDouble /** * EXERCISE * * Build a multi-fiber program that estimates the value of `pi`. Print out * ongoing estimates continuously until the estimation is complete. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = { def printEstimation(inside: Ref[Long], total: Ref[Long]): URIO[Console, Unit] = (inside.get zip total.get).flatMap { case (in, tot) => putStrLn(estimatePi(in, tot).toString) } def updateEstimate(inside: Ref[Long], total: Ref[Long]): URIO[Random, Unit] = randomPoint.flatMap { case (x, y) => val updateTotal = total.update(_ + 1) val updateInside = inside.update(_ + 1) if (insideCircle(x, y)) updateInside zipPar updateTotal else updateTotal } *> updateEstimate(inside, total) def keepEstimating(accuracyRequired: Double, inside: Ref[Long], total: Ref[Long]): URIO[Console, Unit] = (inside.get zip total.get).flatMap { case (in, tot) => val estimate = estimatePi(in, tot) val actual = math.abs(math.Pi - estimate) if (actual <= accuracyRequired) putStrLn(s"Ended with $estimate") else keepEstimating(accuracyRequired, inside, total) } (Ref.make(1L) zip Ref.make(1L)).flatMap { case (in, tot) => (printEstimation(in, tot) *> ZIO.sleep(100.millis)).forever race ZIO.foreachPar_(1 to 10)(_ => updateEstimate(in, tot)) race keepEstimating(accuracyRequired = 0.00001, in, tot) } as 0 } } object ComputePiJohn extends App { import zio.random._ import zio.console._ import zio.clock._ import zio.duration._ import zio.stm._ /** * Some state to keep track of all points inside a circle, * and total number of points. */ final case class PiState( inside: Ref[Long], total: Ref[Long] ) /** * A function to estimate pi. */ def estimatePi(inside: Long, total: Long): Double = (inside.toDouble / total.toDouble) * 4.0 /** * A helper function that determines if a point lies in * a circle of 1 radius. */ def insideCircle(x: Double, y: Double): Boolean = Math.sqrt(x * x + y * y) <= 1.0 /** * An effect that computes a random (x, y) point. */ val randomPoint: ZIO[Random, Nothing, (Double, Double)] = nextDouble zip nextDouble /** * EXERCISE * * Build a multi-fiber program that estimates the value of `pi`. Print out * ongoing estimates continuously until the estimation is complete. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = { def insideDelta(point: (Double, Double)): Int = (if (insideCircle(point._1, point._2)) 1 else 0) def makeEstimator(piState: PiState) = { import piState.{ inside, total } for { point <- randomPoint _ <- total.update(_ + 1) *> inside.update(_ + insideDelta(point)) } yield () } def makeWorker(piState: PiState) = makeEstimator(piState).forever def makeStatusReporter(piState: PiState) = (for { total <- piState.total.get inside <- piState.inside.get _ <- putStrLn(estimatePi(inside, total).toString) _ <- ZIO.sleep(1.second) } yield ()).forever for { _ <- putStrLn("Enter any input to exit...") piState <- (Ref.make(0L) zipWith Ref.make(0L))(PiState(_, _)) singleWorker = makeWorker(piState) workers = List.fill(4)(singleWorker) compositeWorker: URIO[Random, Fiber[Nothing, List[Nothing]]] = ZIO.forkAll(workers) reporter = makeStatusReporter(piState) fiber <- (compositeWorker zipWith reporter.fork)(_ zip _) _ <- getStrLn.orDie *> fiber.interrupt } yield 0 } } object StmSwap extends App { import zio.console._ import zio.stm._ /** * EXERCISE * * Demonstrate the following code does not reliably swap two values in the * presence of concurrency. */ def exampleRef = { def swap[A](ref1: Ref[A], ref2: Ref[A]): UIO[Unit] = for { v1 <- ref1.get v2 <- ref2.get _ <- ref2.set(v1) _ <- ref1.set(v2) } yield () for { ref1 <- Ref.make(100) ref2 <- Ref.make(0) fiber1 <- swap(ref1, ref2).repeat(Schedule.recurs(100)).fork fiber2 <- swap(ref2, ref1).repeat(Schedule.recurs(100)).fork _ <- (fiber1 zip fiber2).join value <- (ref1.get zipWith ref2.get)(_ + _) } yield value } /** * EXERCISE * * Using `STM`, implement a safe version of the swap function. */ def exampleStm = { def swap[A](ref1: TRef[A], ref2: TRef[A]): UIO[Unit] = (for { one <- ref1.get two <- ref2.get _ <- ref2.set(two) _ <- ref1.set(one) } yield ()).commit for { ref1 <- TRef.make(100).commit ref2 <- TRef.make(0).commit fiber1 <- swap(ref1, ref2).repeat(Schedule.recurs(100)).fork fiber2 <- swap(ref2, ref1).repeat(Schedule.recurs(100)).fork _ <- (fiber1 zip fiber2).join value <- (ref1.get zipWith ref2.get)(_ + _).commit } yield value } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = exampleStm.map(_.toString).flatMap(putStrLn) as 0 } object StmLock extends App { import zio.console._ import zio.stm._ /** * EXERCISE * * Using STM, implement a simple binary lock by implementing the creation, * acquisition, and release methods. */ class Lock private (tref: TRef[Boolean]) { def acquire: UIO[Unit] = (tref.get.filter(_ == false) *> tref.set(true)).commit def release: UIO[Unit] = tref.set(false).commit } object Lock { def make: UIO[Lock] = TRef.make(false).map(new Lock(_)).commit } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = (for { lock <- Lock.make fiber1 <- lock.acquire .bracket_(lock.release)(putStrLn("Bob : I have the lock!")) .repeat(Schedule.recurs(10)) .fork fiber2 <- lock.acquire .bracket_(lock.release)(putStrLn("Sarah: I have the lock!")) .repeat(Schedule.recurs(10)) .fork _ <- (fiber1 zip fiber2).join } yield 0) as 1 } object StmQueue extends App { import zio.console._ import zio.stm._ import scala.collection.immutable.{ Queue => ScalaQueue } /** * EXERCISE * * Using STM, implement a async queue with double back-pressuring. */ class Queue[A] private (capacity: Int, queue: TRef[ScalaQueue[A]]) { def take: UIO[A] = (for { q <- queue.get.filter(_.nonEmpty) (head, rest) = q.dequeue _ <- queue.set(rest) } yield head).commit def offer(a: A): UIO[Unit] = (for { q <- queue.get _ <- STM.check(q.length <= capacity) _ <- queue.set(q.enqueue(a)) } yield ()).commit } object Queue { def make[A]: UIO[Queue[A]] = TRef.make(ScalaQueue.empty[A]).map(q => new Queue(capacity = 10, q)).commit } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = for { queue <- Queue.make[Int] _ <- ZIO.foreach(0 to 100)(i => queue.offer(i)).fork _ <- ZIO.foreach(0 to 100)(_ => queue.take.flatMap(i => putStrLn(s"Got: ${i}"))) } yield 0 } object StmLunchTime extends App { import zio.console._ import zio.stm._ /** * EXERCISE * * Using STM, implement the missing methods of Attendee. */ final case class Attendee(state: TRef[Attendee.State]) { import Attendee.State._ def isStarving: STM[Nothing, Boolean] = ??? def feed: STM[Nothing, Unit] = ??? } object Attendee { sealed trait State object State { case object Starving extends State case object Full extends State } } /** * EXERCISE * * Using STM, implement the missing methods of Table. */ final case class Table(seats: TArray[Boolean]) { def findEmptySeat: STM[Nothing, Option[Int]] = seats .fold[(Int, Option[Int])]((0, None)) { case ((index, z @ Some(_)), _) => (index + 1, z) case ((index, None), taken) => (index + 1, if (taken) None else Some(index)) } .map(_._2) def takeSeat(index: Int): STM[Nothing, Unit] = ??? def vacateSeat(index: Int): STM[Nothing, Unit] = ??? } /** * EXERCISE * * Using STM, implement a method that feeds a single attendee. */ def feedAttendee(t: Table, a: Attendee): STM[Nothing, Unit] = for { index <- t.findEmptySeat.collect { case Some(index) => index } _ <- t.takeSeat(index) *> a.feed *> t.vacateSeat(index) } yield () /** * EXERCISE * * Using STM, implement a method that feeds only the starving attendees. */ def feedStarving(table: Table, list: List[Attendee]): UIO[Unit] = ??? def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = { val Attendees = 100 val TableSize = 5 for { attendees <- ZIO.foreach(0 to Attendees)( i => TRef .make[Attendee.State](Attendee.State.Starving) .map(Attendee(_)) .commit ) table <- TArray .fromIterable(List.fill(TableSize)(false)) .map(Table(_)) .commit _ <- feedStarving(table, attendees) } yield 0 } } object StmPriorityQueue extends App { import zio.console._ import zio.stm._ import zio.duration._ /** * EXERCISE * * Using STM, design a priority queue, where smaller integers are assumed * to have higher priority than greater integers. */ class PriorityQueue[A] private ( minLevel: TRef[Int], map: TMap[Int, TQueue[A]] ) { def offer(a: A, priority: Int): STM[Nothing, Unit] = for { min <- minLevel.get _ <- if (priority < min) minLevel.set(priority) else STM.unit optQ <- map.get(priority) q <- optQ.map(STM.succeed).getOrElse(TQueue.make(Int.MaxValue)) _ <- q.offer(a) _ <- map.put(priority, q) } yield () def cleanup(min: Int): STM[Nothing, Unit] = map.delete(min) *> findAndSetNextMinimum def findAndSetNextMinimum: STM[Nothing, Unit] = map.keys.map(_.sorted.headOption) .map(_.fold(Int.MaxValue)(identity)) .flatMap(minLevel.set) def take: STM[Nothing, A] = { for { min <- minLevel.get q <- map.get(min).collect { case Some(tq) => tq } a <- q.take size <- q.size _ <- if (size == 0) cleanup(min) else STM.unit } yield a } } object PriorityQueue { def make[A]: STM[Nothing, PriorityQueue[A]] = for { minLevel <- TRef.make[Int](Int.MaxValue) map <- TMap.empty[Int, TQueue[A]] } yield new PriorityQueue(minLevel, map) } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = (for { _ <- putStrLn("Enter any key to exit...") queue <- PriorityQueue.make[String].commit lowPriority = ZIO.foreach(0 to 100) { i => ZIO.sleep(1.millis) *> queue .offer(s"Offer: ${i} with priority 3", 3) .commit } highPriority = ZIO.foreach(0 to 100) { i => ZIO.sleep(2.millis) *> queue .offer(s"Offer: ${i} with priority 0", 0) .commit } _ <- ZIO.forkAll(List(lowPriority, highPriority)) *> queue.take.commit .flatMap(putStrLn(_)) .forever .fork *> getStrLn } yield 0).fold(_ => 1, _ => 0) } object StmReentrantLock extends App { import zio.console._ import zio.stm._ private final case class WriteLock( writeCount: Int, readCount: Int, fiberId: FiberId ) private final class ReadLock private (readers: Map[Fiber.Id, Int]) { def total: Int = readers.values.sum def noOtherHolder(fiberId: FiberId): Boolean = readers.size == 0 || (readers.size == 1 && readers.contains(fiberId)) def readLocks(fiberId: FiberId): Int = readers.get(fiberId).fold(0)(identity) def adjust(fiberId: FiberId, adjust: Int): ReadLock = { val total = readLocks(fiberId) val newTotal = total + adjust new ReadLock( readers = if (newTotal == 0) readers - fiberId else readers.updated(fiberId, newTotal) ) } } private object ReadLock { val empty: ReadLock = new ReadLock(Map()) def apply(fiberId: Fiber.Id, count: Int): ReadLock = if (count <= 0) empty else new ReadLock(Map(fiberId -> count)) } /** * EXERCISE * * Using STM, implement a reentrant read/write lock. */ class ReentrantReadWriteLock(data: TRef[Either[ReadLock, WriteLock]]) { def writeLocks: UIO[Int] = data.get.map(_.fold(_ => 0, _.writeCount)).commit def writeLocked: UIO[Boolean] = writeLocks.map(_ > 0) def readLocks: UIO[Int] = data.get.map(_.fold(_.total, _.readCount)).commit def readLocked: UIO[Boolean] = readLocks.map(_ > 0) val read: Managed[Nothing, Int] = ??? val write: Managed[Nothing, Int] = ??? } object ReentrantReadWriteLock { def make: UIO[ReentrantReadWriteLock] = TRef .make[Either[ReadLock, WriteLock]](Left(ReadLock.empty)) .map(tref => new ReentrantReadWriteLock(tref)) .commit } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = ??? } object StmDiningPhilosophers extends App { import zio.console._ import zio.stm._ sealed trait Fork val Fork = new Fork {} final case class Placement(left: TRef[Option[Fork]], right: TRef[Option[Fork]]) final case class Roundtable(seats: Vector[Placement]) /** * EXERCISE * * Using STM, implement the logic of a philosopher to take not one fork, but * both forks when they are both available. */ def takeForks(left: TRef[Option[Fork]], right: TRef[Option[Fork]]): STM[Nothing, (Fork, Fork)] = for { l <- left.get.collect { case Some(f) => f } r <- right.get.collect { case Some(f) => f } } yield (l, r) def putForks(left: TRef[Option[Fork]], right: TRef[Option[Fork]])(tuple: (Fork, Fork)) = { val (leftFork, rightFork) = tuple right.set(Some(rightFork)) *> left.set(Some(leftFork)) } def setupTable(size: Int): ZIO[Any, Nothing, Roundtable] = { val makeFork = TRef.make[Option[Fork]](Some(Fork)) (for { allForks0 <- STM.foreach(0 to size) { i => makeFork } allForks = allForks0 ++ List(allForks0(0)) placements = (allForks zip allForks.drop(1)).map { case (l, r) => Placement(l, r) } } yield Roundtable(placements.toVector)).commit } def eat(philosopher: Int, roundtable: Roundtable): ZIO[Console, Nothing, Unit] = { val placement = roundtable.seats(philosopher) val left = placement.left val right = placement.right for { forks <- takeForks(left, right).commit _ <- putStrLn(s"Philosopher ${philosopher} eating...") _ <- putForks(left, right)(forks).commit _ <- putStrLn(s"Philosopher ${philosopher} is done eating") } yield () } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = { val count = 10 def eaters(table: Roundtable): Iterable[ZIO[Console, Nothing, Unit]] = (0 to count).map { index => eat(index, table) } for { table <- setupTable(count) fiber <- ZIO.forkAll(eaters(table)) _ <- fiber.join _ <- putStrLn("All philosophers have eaten!") } yield 0 } } object Interview extends App { import zio.console._ val questions = "Where where you born?" :: "What color are your eyes?" :: "What is your favorite movie?" :: "What is your favorite number?" :: Nil /** * EXERCISE * * Using `ZIO.foreach`, iterate over all of the `questions`, and for each * question, print out the question, and read the answer from the console * using `getStrLn`, collecting all of the answers into a list. * * Print out the answers when done. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = ZIO.foreach(questions) { q => putStrLn(q) *> getStrLn } .flatMap(answers => putStrLn(s"These are all the answers you put in ${answers.mkString(",")}")) .fold(_ => 1, _ => 0) } object SimpleActor extends App { import zio.console._ import zio.stm._ sealed trait Command case object ReadTemperature extends Command final case class AdjustTemperature(value: Double) extends Command type TemperatureActor = Command => Task[Double] /** * EXERCISE * * Using ZIO Queue and Promise, implement the logic necessary to create an * actor as a function from `Command` to `Task[Double]`. */ def makeActor(initialTemperature: Double): UIO[TemperatureActor] = { type Bundle = (Command, Promise[Nothing, Double]) ??? } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = { val temperatures = (0 to 100).map(_.toDouble) (for { actor <- makeActor(0) _ <- ZIO.foreachPar(temperatures) { temp => actor(AdjustTemperature(temp)) } temp <- actor(ReadTemperature) _ <- putStrLn(s"Final temperature is ${temp}") } yield 0) orElse ZIO.succeed(1) } } object ParallelFib extends App { import zio.console._ def fib(n: Int): UIO[BigInt] = if (n <= 1) UIO(n) else UIO.unit.flatMap { _ => (fib(n - 1) zipWithPar fib(n - 2))(_ + _) } def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = for { _ <- putStrLn("What number of the fibonacci sequence should we calculate?") n <- getStrLn.orDie.flatMap(input => ZIO(input.toInt)).eventually f <- fib(n) _ <- putStrLn(s"fib(${n}) = ${f}") } yield 0 } object Sharding extends App { /** * EXERCISE * * Create N workers reading from a Queue, if one of them fails, then wait * for the other ones to process their current item, but terminate all the * workers. * * Return the first error, or never return, if there is no error. */ def shard[R, E, A]( queue: Queue[A], n: Int, worker: A => ZIO[R, E, Unit] ): ZIO[R, Nothing, E] = { val qWorker = queue.take.flatMap(worker).forever val qWorkers = List.fill(n)(qWorker) val program = for { fiber <- ZIO.forkAll(qWorkers) list <- fiber.join nothing <- list.headOption.fold(ZIO.dieMessage(s"You supplied n=$n"))(identity) } yield nothing program.flip } def run(args: List[String]) = ??? } object CustomEnvironment extends App { import zio.console._ import java.io.IOException type MyFx = Logging with Files trait Logging { val logging: Logging.Service } object Logging { trait Service { def log(line: String): UIO[Unit] } def log(line: String) = ZIO.accessM[Logging](_.logging.log(line)) } trait Files { val files: Files.Service } object Files { trait Service { def read(file: String): IO[IOException, String] } def read(file: String) = ZIO.accessM[Files](_.files.read(file)) } val effect: ZIO[Logging with Files, IOException, Unit] = (for { file <- Files.read("build.sbt") _ <- Logging.log(file) } yield ()) def run(args: List[String]) = { effect.provide { new Logging with Files { override val logging: Logging.Service = new Logging.Service { override def log(line: String): UIO[Unit] = UIO(println(line)) } override val files: Files.Service = new Files.Service { override def read(file: String): IO[IOException, String] = Task(scala.io.Source.fromFile(file).mkString).refineToOrDie[IOException] } } }.fold(_ => 1, _ => 0) } } object Hangman extends App { import Dictionary.Dictionary import zio.console._ import zio.random._ import java.io.IOException /** * EXERCISE * * Implement an effect that gets a single, lower-case character from * the user. */ lazy val getChoice: ZIO[Console, IOException, Char] = getStrLn.map(_.trim.toLowerCase().toList).flatMap { case h :: Nil if h.isLetterOrDigit => ZIO.succeed(h) case _ => putStrLn("You did not enter a single valid character") *> getChoice } /** * EXERCISE * * Implement an effect that prompts the user for their name, and * returns it. */ lazy val getName: ZIO[Console, IOException, String] = putStrLn("What's your name?") *> getStrLn /** * EXERCISE * * Implement an effect that chooses a random word from the dictionary. * The dictionary is `Dictionary.Dictionary`. */ lazy val chooseWord: ZIO[Random, Nothing, String] = nextInt(Dictionary.length).map(Dictionary) /** * EXERCISE * * Implement the main game loop, which gets choices from the user until * the game is won or lost. */ def gameLoop(oldState: State): ZIO[Console, IOException, Unit] = for { c <- getChoice newState = oldState.addChar(c) result = analyzeNewInput(oldState, newState, c) _ <- renderState(newState) _ <- result match { case GuessResult.Incorrect => putStrLn(s"Wrong answer ${newState.name}, try again!") *> gameLoop(newState) case GuessResult.Unchanged => putStrLn("You already guessed that, try again!") *> gameLoop(newState) case GuessResult.Correct => putStrLn("Correct! keep going") *> gameLoop(newState) case GuessResult.Lost => putStrLn("Sorry! you lost") case GuessResult.Won => putStrLn("Yay, you win!") } } yield () def renderState(state: State): ZIO[Console, Nothing, Unit] = { /** * * f n c t o * - - - - - - - * * Guesses: a, z, y, x * */ val word = state.word.toList .map(c => if (state.guesses.contains(c)) s" $c " else " ") .mkString("") val line = List.fill(state.word.length)(" - ").mkString("") val guesses = " Guesses: " + state.guesses.mkString(", ") val text = word + "\n" + line + "\n\n" + guesses + "\n" putStrLn(text) } final case class State(name: String, guesses: Set[Char], word: String) { final def failures: Int = (guesses -- word.toSet).size final def playerLost: Boolean = failures > 10 final def playerWon: Boolean = (word.toSet -- guesses).isEmpty final def addChar(char: Char): State = copy(guesses = guesses + char) } sealed trait GuessResult object GuessResult { case object Won extends GuessResult case object Lost extends GuessResult case object Correct extends GuessResult case object Incorrect extends GuessResult case object Unchanged extends GuessResult } def analyzeNewInput(oldState: State, newState: State, char: Char): GuessResult = if (oldState.guesses.contains(char)) GuessResult.Unchanged else if (newState.playerWon) GuessResult.Won else if (newState.playerLost) GuessResult.Lost else if (oldState.word.contains(char)) GuessResult.Correct else GuessResult.Incorrect /** * EXERCISE * * Implement hangman using `Dictionary.Dictionary` for the words, * and the above helper functions. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = (for { name <- getName word <- chooseWord state = State(name, Set(), word) _ <- renderState(state) _ <- gameLoop(state) } yield 0) orElse ZIO.succeed(1) } /** * GRADUATION PROJECT * * Implement a game of tic tac toe using ZIO, then develop unit tests to * demonstrate its correctness and testability. */ object TicTacToe extends App { import zio.console._ sealed trait Mark { final def renderChar: Char = this match { case Mark.X => 'X' case Mark.O => 'O' } final def render: String = renderChar.toString } object Mark { case object X extends Mark case object O extends Mark } final case class Board private (value: Vector[Vector[Option[Mark]]]) { /** * Retrieves the mark at the specified row/col. */ final def get(row: Int, col: Int): Option[Mark] = value.lift(row).flatMap(_.lift(col)).flatten /** * Places a mark on the board at the specified row/col. */ final def place(row: Int, col: Int, mark: Mark): Option[Board] = if (row >= 0 && col >= 0 && row < 3 && col < 3) Some( copy(value = value.updated(row, value(row).updated(col, Some(mark)))) ) else None /** * Renders the board to a string. */ def render: String = value .map(_.map(_.fold(" ")(_.render)).mkString(" ", " | ", " ")) .mkString("\n---|---|---\n") /** * Returns which mark won the game, if any. */ final def won: Option[Mark] = if (wonBy(Mark.X)) Some(Mark.X) else if (wonBy(Mark.O)) Some(Mark.O) else None private final def wonBy(mark: Mark): Boolean = wonBy(0, 0, 1, 1, mark) || wonBy(0, 2, 1, -1, mark) || wonBy(0, 0, 0, 1, mark) || wonBy(1, 0, 0, 1, mark) || wonBy(2, 0, 0, 1, mark) || wonBy(0, 0, 1, 0, mark) || wonBy(0, 1, 1, 0, mark) || wonBy(0, 2, 1, 0, mark) private final def wonBy( row0: Int, col0: Int, rowInc: Int, colInc: Int, mark: Mark ): Boolean = extractLine(row0, col0, rowInc, colInc).collect { case Some(v) => v }.toList == List .fill(3)(mark) private final def extractLine( row0: Int, col0: Int, rowInc: Int, colInc: Int ): Iterable[Option[Mark]] = for { row <- (row0 to (row0 + rowInc * 2)) col <- (col0 to (col0 + colInc * 2)) } yield value(row)(col) } object Board { final val empty = new Board(Vector.fill(3)(Vector.fill(3)(None))) def fromChars( first: Iterable[Char], second: Iterable[Char], third: Iterable[Char] ): Option[Board] = if (first.size != 3 || second.size != 3 || third.size != 3) None else { def toMark(char: Char): Option[Mark] = if (char.toLower == 'x') Some(Mark.X) else if (char.toLower == 'o') Some(Mark.O) else None Some( new Board( Vector( first.map(toMark).toVector, second.map(toMark).toVector, third.map(toMark).toVector ) ) ) } } val TestBoard = Board .fromChars( List(' ', 'O', 'X'), List('O', 'X', 'O'), List('X', ' ', ' ') ) .get .render /** * The entry point to the game will be here. */ def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = putStrLn(TestBoard) as 0 }