Stream.Transformation
A Transformation is an in-memory stream that changes the values
passing through it, and this module is where transformations are made: ones that pass values on
unchanged, ones driven by an Elm function, and built-in ones for text and compression.
Stream.Transformation.gunzip
|> Task.andThen (\gunzip -> Stream.pipeThrough gunzip compressed)Everything here that makes a transformation is a Task, not a Transformation. Each run of the
task creates a new transformation with nothing in it. readable and
writable give its two sides: values written into the writable side come out,
transformed, on the readable side. Reading, writing, closing, cancelling and piping work on those
sides as Stream describes.
Each side has a capacity, a number of values. The writable side holds up to its capacity of
values written but not yet transformed, and the readable side holds results not yet read. A value
is transformed as it leaves the writable side, which happens only while the readable side holds
fewer results than its capacity (or, at a read capacity of 0, while a reader waits; see
customWith), so a reader that falls behind fills the readable side first, then
the writable side, after which a write waits for room, as Stream.write
describes. A Stream.write into a transformation succeeds once its value has
been transformed, not once the results have been read.
A transformation can end early in two ways, and both act on both sides: results not yet read
are dropped, and writes still waiting, later writes, a close and later reads fail, apart from
bytes Stream.readLine has kept, which can still be read.
- A custom transformation whose step returns
Cancelis cancelled, and these fail withStream.Cancelledand the step's reason. - A decompressor that cannot go on has failed, as each decompressor describes, and these fail
with
Stream.Failedand aStream.Failuredescribing the problem, whichStream.failureToStringreads.
Building Transformations
An identity transformation passes every value on unchanged. A custom transformation calls an
Elm function, its step, for each value written, with a state the step carries from one value to
the next; CustomAction is what a step returns.
identity : Task x (Transformation data data)identityWith :
{ readCapacity : Int, writeCapacity : Int }
-> Task x (Transformation data data)Creates an identity transformation, as identity does, with the given
capacities. A capacity below 1 is raised to 1.
null : data -> Task x (Transformation data data)Creates a transformation that accepts every value written into it and drops it. Its readable side never gives a value, and is closed when the writable side is closed.
The argument becomes the step's state, which nothing reads, so any value will do. Each side has a capacity of one value.
type CustomAction state value
= UpdateState state
| Send { state : state, send : List value }
| Close (List value)
| Cancel StringWhat a custom transformation's step does with one value written into it: carry on with a new state, possibly producing values, or end the transformation.
UpdateState carries the new state and produces nothing.
Send carries the new state and the values to make readable, in order. All of them are put in
the readable side's buffer, even beyond its capacity; no further value is transformed until
readers have brought the buffer back below its capacity, or, at a read capacity of 0, emptied it
and wait for more.
Close ends the transformation after the values it carries. They are made readable, and once
they and the values before them have been read, reads fail with Stream.Closed. Writes still
waiting to be transformed, a close in progress, and every later write or close fail with
Stream.Closed too.
Cancel cancels the transformation on both sides with the given reason, so that reads, writes
and closes fail with Stream.Cancelled and that reason, as the module documentation describes.
With Close and Cancel, the write of the value that produced them still succeeds.
custom :
state -> input -> CustomAction state output
-> state
-> Task x (Transformation input output)Creates a custom transformation. For each value written, in order, the step is called with the
current state and the value, and the CustomAction it returns says what happens.
The second argument is the first state. Each side has a capacity of one value.
The step is called only for values written. Closing the writable side does not call it, so anything the step holds in its state, such as a partial batch, is not produced at the close; the readable side closes once its buffered values have been read.
customWith :
state -> input -> CustomAction state output
-> { initialState : state, readCapacity : Int, writeCapacity : Int }
-> Task x (Transformation input output)Creates a custom transformation, as custom does, with the given first state and
capacities.
A write capacity below 1 is raised to 1, and a read capacity below 0 to 0. With a read capacity of 0, a value is transformed only when nothing is buffered on the readable side and a read is waiting on it, or a pipe out of it is ready for a value.
Using Transformations
readable : Transformation input output -> Readable outputReturns the readable side of a transformation, from which its output values are read.
writable : Transformation input output -> Writable inputReturns the writable side of a transformation, into which its input values are written.
Text
The text transformations convert between String and UTF-8 Bytes, and never fail. Neither
produces an empty value: an input that yields nothing yet, such as an empty string, produces no
output. Each side has a capacity of one value.
textEncoder : Task x (Transformation String Bytes)A task that creates a transformation from Strings to their UTF-8 encoding as Bytes.
A surrogate pair split across two strings is encoded as the one character it stands for. A lone
surrogate becomes U+FFFD, on both backends, unlike Stream.writeString.
textDecoder : Task x (Transformation Bytes String)A task that creates a transformation from UTF-8 Bytes to Strings.
The bytes may be split anywhere: a character split across two chunks is decoded whole. Invalid bytes are replaced by the replacement character U+FFFD, rather than failing the transformation, and so is a character left incomplete when the writable side is closed. A byte order mark at the very start is removed.
Compression
Six transformations from Bytes to Bytes, using zlib, in three formats: gzip, deflate data in
the zlib format, which adds a header and a checksum, and raw deflate data, which has neither. Each
side has a capacity of one value, but one value written can produce several outputs. Each output is
at most 64 KiB.
A compressor does not produce output for every value written: zlib holds data back until it has enough to emit, and writes the rest when the writable side is closed, so a reader may get nothing until then.
A decompressor that fails ends the transformation with a Stream.Failed failure describing the
problem, as described above. A write of the value that carried the bad data fails, and a close
fails when the compressed data was incomplete.
gzip : Task x (Transformation Bytes Bytes)A task that creates a transformation that compresses Bytes into the gzip format.
gunzip : Task x (Transformation Bytes Bytes)A task that creates a transformation that decompresses gzip-format Bytes.
The transformation fails, with Stream.Failed, when the data cannot be decompressed, or when
the writable side is closed before the compressed data is complete. Data after the end of the
compressed data is treated differently by the two backends: the native backend fails the
transformation; on the JavaScript backend, whether such data is decompressed, ignored or fails
the transformation depends on the data and on how it was split into written values.
deflate : Task x (Transformation Bytes Bytes)A task that creates a transformation that compresses Bytes into deflate data in the zlib
format.
inflate : Task x (Transformation Bytes Bytes)A task that creates a transformation that decompresses deflate data in the zlib format.
The transformation fails, with Stream.Failed, when the data cannot be decompressed, or when
the writable side is closed before the compressed data is complete. Data after the end of the
compressed data fails it on the native backend, and is ignored on the JavaScript backend.
deflateRaw : Task x (Transformation Bytes Bytes)A task that creates a transformation that compresses Bytes into raw deflate data, with no
header or checksum.
inflateRaw : Task x (Transformation Bytes Bytes)A task that creates a transformation that decompresses raw deflate data, which has no header or checksum.
The transformation fails, with Stream.Failed, when the data cannot be decompressed, or when
the writable side is closed before the compressed data is complete. Data after the end of the
compressed data fails it on the native backend, and is ignored on the JavaScript backend.
A task that creates an identity transformation, whose sides each have a capacity of one value.
Handing its readable side to another part of a program gives that part a one-way channel from whoever holds the writable side.