Introduction To ZChannels
Channels are the nexus of communications, which support both reading and writing. They allow us to have a unidirectional flow of data from the input to the output.
A ZChannel[-Env, -InErr, -InElem, -InDone, +OutErr, +OutElem, +OutDone] requires some environment Env and have two main operations:
-
It can read some data
InElemfrom the input port, and finally can terminate with a done value of typeInDone. If the read operation fails, the channel will terminate with an error of typeInErr. -
It can write some data
OutElemto the output port, and finally terminate the channel with a done value of typeOutDone. If the write operation fails, the channel will terminate with an error of typeOutErr.
They are an underlying abstraction for ZStream, ZPipeline, and ZSink. In ZIO Streams, we call the input port ZStream, the output port ZSink, and the middle part ZPipeline:
-
A
Channelcan write some elements to the output, and it can terminate with some sort of done value. TheChanneluses this done value to notify the downstreamChannelthat its emission of elements is finished. In ZIO, theZStreamis encoded as an output side of theChannel. -
A
Channelcan read from its input, and it can also terminate with some sort of done value, which is an upstream result. So aChannelhas the input type, and the input done type. TheChanneluses this done value to determine when the upstreamChannelfinishes its emission. In ZIO, theZSinkis encoded as an input side of theChannel. -
A
Channelcan read from its input, do some transformation on the elements, and write to its output. In ZIO, theZPipelineis encoded as a middle part of both sides of theChannel. Pipelines accept a stream as input and return the transformed stream as output.
ZChannel is an underlying abstraction. So we do not usually need to use it directly. So if you are learning ZIO Streams, we recommend you to focus on ZStream, ZPipeline, and ZSink data types.
Let's take a look at how ZStream, ZPipeline and ZSink are defined using ZChannel:
trait ZChannel[-Env, -InErr, -InElem, -InDone, +OutErr, +OutElem, +OutDone]
case class ZStream[-R, +E, +A] (
val channel: ZChannel[R, Any, Any, Any, E, Chunk[A], Any]
)
case class ZSink[-R, +E, -In, +L, +Z] (
val channel: ZChannel[R, ZNothing, Chunk[In], Any, E, Chunk[L], Z]
)
case class ZPipeline[-R, +E, -In, +Out] (
val channel: ZChannel[R, ZNothing, Chunk[In], Any, E, Chunk[Out], Any]
)
So we can say that:
-
ZStream[R, E, A]is a channel that requires an environmentR, emits elements of typeChunk[A], and terminates, if at all, with either an error of typeEor a done value of typeAny. -
ZPipeline[R, Err, In, Out]is a channel that usesRas its environment, consumesChunk[In]from its input port, and producesChunk[Out]to its output port. -
ZSink[R, E, In, L , Z]is a channel that usesRas its environment, consumesChunk[In]from its input port, and producesChunk[L]to its output port as its leftovers, and can terminate with a success value of typeZor can terminate with a failure of typeE.
Channels compose in a variety of ways:
-
Piping— One channel can be piped to another channel, assuming the input type of the second is the same as the output type of the first. We can pipe data from a channel that reads from the input port to a channel that writes to the output port, by using the
pipeToor>>>operator. -
Sequencing— The terminal value of one channel can be used to create another channel, and both the first channel and the function that makes the second channel can be composed into a channel. We use the
ZChannel#flatMapto sequence the channels. -
Concating— The output of one channel can be used to create other channels, which are all concatenated together. The first channel and the function that makes the other channels can be composed into a channel. We use
ZChannel#concat*operators to do this.
Finally, we can run a channel by using the ZChannel#run* operators.