A ZIO cheatsheet

[toc]

January, 2023 Update: This ZIO cheatsheet is based on ZIO 1.x, and it is very much out of date now. For more modern information, see my book, Learn Functional Programming Without Fear.

As a very brief introduction, this is the start of a Scala ZIO cheat sheet. If you want a good cheat sheet right now, see this one on github. I’m creating my own as I learn ZIO and read the Zionomicon book. During the learning process I find that it’s much better to create your own by hand, that way you get something that’s meaningful to you.

Note that almost all of these initial examples come from the ZIOnomicon book and the video that I link to later.

This post is sponsored by my new book,
Learn Functional Programming Without Fear.

ZIO + Scala CLI + Scala 3

November, 2022 update: Here are two ZIO + Scala CLI + Scala 3 “Hello, world” examples.

ZIO build.sbt configuration

Use the latest stable version:

libraryDependencies += "dev.zio" %% "zio" % "1.0.5"

See the ZIO Getting Started page for more info. It also shows zio.App and zio.console. examples.

How to create a ZIO.App

How to create and run a ZIO.App:

import zio._
object GroceryStore extends App {
    def run(args: List[String]) = goShopping.exitCode
}

// exitCode turns all successes into ExitCode(0) and 
// failures into ExitCode(1)

ZIO Console I/O

When many of us start with a new technology, we start with “Hello, world” examples of reading input and printing output. In ZIO 2, you use the Console type for those actions. Console has these functions for console I/O:

  • print            // like 'print'
  • printError       // like 'System.err.print'
  • printLine         // like 'println'
  • printLineError   // like 'System.err.println'
  • readLine         // read input

See the ZIO Console page for more details.

Sequential composition/computation

Methods like flatMap and zip* let you get back to sequential computation, just like procedural programming, i.e., doing one thing after another:

import scala.io.StdIn
val readLine = ZIO.effect(StdIn.readLine())
def printLine(line: String) = ZIO.effect(println(line))

// execute readLine, then pass its result to printLine.
// flatMap can be read like “and then do Expression2 with
// the result of Expression1”:
val echo = readLine.flatMap(line => printLine(line))
  • you can chain a bunch of flatMap’s together like this
  • but it’s easier to read a for-expression/comprehension
  • that last line of code is equivalent to this:
import zio._
val echo = for {
    line <- readLine
    _    <- printLine(line)
} yield ()

Other sequential operators

Operator Description
zipWith combine two effects sequentially; merge their two results with an anonymous function (or similar)
zip combine two effects sequentially, yield a tuple
zipLeft combine two effects sequentially, return the first’s result
zipRight combine two effects sequentially, return the second’s result
foreach return a single effect that describes the process of performing an effect for each element of a sequence
collectAll return “a single effect that collects the results of a whole collection of effects”
  • zipWith syntax:
val firstName = ZIO.effect(StdIn.readLine("..."))
val lastName = ZIO.effect(StdIn.readLine("..."))
val fullName = firstName.zipWith(lastName)((first, last) => s"$first $last")

How it works:

val fullName = firstName.zipWith(lastName)((first, last) => s"$first $last")
                  ^                 ^                   ^
               effect #1        effect #2     merge them w/ this function

Here’s an example that shows ZIO’s flatMap and foreach:

override val run = program.flatMap { (lines: Seq[String]) =>
    ZIO.foreach(lines) { line =>
        Console.printLine(line)
    }
}

For my own benefit, here’s the complete ZIO 2 application that code comes from:

object ZioReadFile extends ZIOAppDefault:

    val filename = "stocks.dat"

    val program = for
        linesFromFile: Seq[String] <- ZIO.fromTry(readFile(filename))
        cleanLines: Seq[String] = getRidOfUndesiredLines(linesFromFile)
    yield
        cleanLines

    override val run = program.flatMap { (lines: Seq[String]) =>
        ZIO.foreach(lines) { line =>
            Console.printLine(line)
        }
    }

There may be better ways to do that, but that’s what I’m doing today. See the ZIO Control Flow page for more details and examples of ZIO’s conditional and loop operators.

Aliases

Method Alias
zipLeft <*
zipRight *>
  • Example:

Combine two effects sequentially and return the Unit success value of the right-hand effect:

val helloWorld =
    ZIO.effect(print("Hello, ")) *> ZIO.effect(print("World!\n"))

ZIO type parameters

Type Description
R The environment
E The possible error result type
A The possible (hopeful) success result type

See the ZIO Overview page for more details.

ZIO type aliases

Type aliases you can use instead of the full ZIO type signature:

Alias Full Type Signature
IO [E, A] ZIO[Any, E, A]
Task[A] ZIO[Any, Throwable, A]
RIO [R, A] ZIO[R, Throwable, A]
UIO [A] ZIO[Any, Nothing, A]
URIO[R, A] ZIO[R, Nothing, A]

See the ZIO Overview page for more details.

delay, run later, timer/clock

// ZIOnomicon example
import zio.clock._
import zio.duration._

val goShoppingLater = goShopping.delay(1.hour)

A Tour of ZIO (video)

These are my notes from watching the A Tour of ZIO video. They’re not very organized, but hopefully I’ll fix them up one day.

ZIO is based on fiber-based concurrency:

  • fibers are coming to the JVM via Project Loom
  • until then, you can use libraries like ZIO to get fibers now
  • ZIO is 100% async
    • even code that looks like it’s blocking isn’t blocking
  • uses type system to catch bugs at compile time when they’re easiest and cheapest to fix

Most important data type is called ZIO:

  • ZIO[-R, +E, +A]
    • can be thought of as a functional effect, or just effect
    • can be thought of as an immutable value
    • represents a job or workflow or task
    • can think of an effect as being a lazy description of various types of interactions with the outside world
      • printing text, databases, networks, etc.
    • ZIO effects are 100% lazy
    • nothing has happened; have to run “execution” to translate your description of the interaction with the outside world into actual interaction; that’s called running or executing the effect
    • do that with one of the “unsafe” method
    • R represents an environment you’re passing in

Mental model:

  • ZIO[R,E,A]
    • a function of the R parameter to an Either[E, A]
      • to either an E or an A
      • function takes an R
      • E is a failure type, A is a success type
      • you give the functions an R and get an E or an A
        • always get a failure or a success
    • an effect can be thought of as requiring some environment type R, which can be a database connection or some configuration or an http connection or spark client

ZIO has five type aliases that are common simplifications of ZIO[R,E,A]:

  • Five type aliases:
    • Task[+A] = ZIO[Any, Throwable, A]
      • an effect that doesn’t need anything
      • Any lets you model an effect that doesn’t need anything
    • UIO[+A] = ZIO[Any, Nothing, A]
      • doesn’t need anything
      • cannot fail (Nothing)
      • Nothing means the effect can’t fail
      • Nothing is a special type, there are no values of it
    • RIO[-R, +A] = ZIO[R, Throwable, A]
    • IO[+E, +A] = ZIO[Any, E, A]
      • does not need anything
      • fails with E or succeeds with A
    • URIO[-R,+A] = ZIO[R, Nothing, A]

zio.App:

  • zio.App
  • App expects you to override run
  • it also bakes in a runtime that executes the effect that is returned from run
  • run:
    • def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = ...

HelloWorld:

  • have to return an effect from run
  • putStrLn prints a line of text and returns an effect
// an interactive application
object PromptName extends App {
    import zio.console._
    def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = 
        putStrLn("What is your name? ") *>
        // can’t use zipRight operator here; if you want to do several
        // things in sequence, and what you’re working on depends on
        // the return/success values of what came before, you need to 
        // use flatMap, which is sometimes known as `chain` or `andThen`
        // in other languages
        getStrLn.flatMap(name => putStrLn(s"Hello, $name")).fold(
            _ => 1,
            _ => 0
        )
        
        // more readable:
        (for {
            // use _ because you don’t care about the Unit value
            _    <- putStrLn("What is your name? ")  // flatMap
            name <- getStrLn                         // flatMap
            _    <- putStrLn(s"Hello, $name")        // map
        } yield 0) orElse ZIO.succeed(1)
}

Resources