Other Sequential Operators
Sequential composition is so common when using functional effects, ZIO provides a variety of related operators for common needs.
The most basic of these is zipWith, which combines two effects sequentially, merging their two results with the specified user-defined function.
For example, if we have two effects that prompt for the user’s first name and last name, then we can use zipWith to combine these effects together sequentially, merging their results into a single string:
val firstName = ZIO.attempt(StdIn.readLine("What is your first name?"))
val lastName = ZIO.attempt(StdIn.readLine("What is your last name"))
val fullName = firstName.zipWith(lastName)((first, last) => s"$first $last")The zipWith operator is less powerful than flatMap because it does not allow the second effect to depend on the first, even though the operator still describes sequential, left-to-right composition.
Other variations include zip, which sequentially combines the results of two effects into a tuple of their results; zipLeft, which sequentially combines two effects, returning the result of the first; and zipRight, which sequentially combines two effects returning the result of the second.
Occasionally, you will see <* used as an alias for zipLeft, and *> as an alias for zipRight. These operators are particularly useful for combining a number of effects sequentially when the result of one or more of the effects is not needed.
For example, in the following snippet, we sequentially combine two effects, returning the Unit success value of the right-hand effect:
val helloWorld = ZIO.attempt(print("Hello, ")) *> ZIO.attempt(print("World!\n"))This is useful because even while Unit is not a very useful success value, a tuple of unit values is even less useful!
Another useful set of sequential operators is foreach and collectAll.
The foreach operator returns a single effect that describes performing an effect for each element of a collection in sequence. It’s similar to a for loop in procedural programming, which iterates over values, processes them in some fashion, and collects the results.
For example, we could create an effect that describes printing all integers between 1 and 100 like this:
val printNumbers = ZIO.foreach(1 to 100) { n => printLine(n.toString) }Similarly, collectAll returns a single effect that collects the results of a whole collection of effects. We could use this to collect the results of a number of printing effects, as shown in the following snippet:
val prints = List( printLine("The"), printLine("quick"), printLine("brown"), printLine("fox") )
val printWords = ZIO.collectAll(prints)With just what you have learned so far, you can take any procedural program and translate it into a ZIO program by wrapping statements in effect constructors and combining them with flatMap.
If that is all you do, you won’t be taking advantage of all the features that ZIO has to offer, but it’s a place to start, and it can be a useful technique when migrating legacy code to ZIO.
